上一篇文章:使用 AHK 在 VS Code 中根據上下文自動切換輸入法狀態 給出一個使用 ahk 在 VSCode 自動切換輸入法的方法。不過這個方法實際上很蹩腳,一點都不優(zhuang)雅(bi)。一直想能不能直接使用 js 實現這個,但是 js 大多數是用來搞前端,對 winAPI
沒什么支持,因此頗費了一番周折。
直到發現一個可以用來調用 winAPI
的包叫做 node-ffi
,以及它的 升級版 node-ffi-napi
,才算是拿到了這個接口。測試性能比原來的好了十倍,使用 AHK 大概需要 20毫秒,現在約1-3 毫秒就能完成切換。
效果和原來大同小異,但是這次 直接安裝插件即可,無需其他操作:
插件已經發布到插件市場:Shift IM for Math
。如果不關心實現過程,那么 Enjoy it!
(原來的插件 Ultra IME toggler
不再維護,僅作學習交流使用)
漫漫搜尋
其實從去年的這個時候剛剛接觸 js 時,就萌生了這個想法,無奈那時連 Node.js 還都不知道,也不知道去哪搜索。前段時間使用 AHK 成功實現自動輸入法切換,然后這個想法重新回來了,決定花點時間解決這個問題。
翻了一下 AHK 的幫助文檔,了解到上次提到的核心代碼:
hWnd := winGetID("A")
DefaultIMEWnd := DllCall("imm32\ImmGetDefaultIMEWnd", "Uint", hWnd , "Uint")
SendMessage(
0x283, ; Message : WM_IME_CONTROL
0x002, ; wParam : IMC_SETCONVERSIONMODE
1025, ; lParam : (1025-CN; 0-EN)
, ; Control : (Window)
; Retrieves the default window handle to the IME class.
"ahk_id " DefaultIMEWnd
)
實際上 SendMessage
以及 ImmGetDefaultIMEWnd
都是C中定義的函數,而AHK直接把它拿來用了。我們看一下這個輸入法控制的腳本是怎么工作的:
- 使用
winGetID
拿到當前窗口句柄 - 使用
ImmGetDefaultIMEWnd
拿到對應輸入法的句柄 - 向輸入法發送消息
- 發送消息為 “控制輸入法”
WM_IME_CONTROL
,值為0x283
wParam
參數給0x002
,用以設定輸入法狀態lParam
參數給1025
或0
,分別對應中英文
- 發送消息為 “控制輸入法”
接下來問題,就是如何將這一流程搬到 JS 上去。搜索一番后,找到了這個包:node-ffi
,可惜已經停更了,沒法在新 node
上跑。於是改用 node-ffi-napi
包。
安裝問題
以下默認裝好了 Node.js
和 npm
。
話不多說,直接 npm install ffi-napi
。然后,運氣不好,一串鮮紅的 ERR 讓人心涼了半截!當然去搜報錯,才搜到裝這個包的正確姿勢。
首先,由於需要調系統的API,包含 C++ 代碼,所以肯定需要編譯的,因此首先下載 windows-build-tools
,這是個 VS 2017 的編譯套件,還有 python 2.x
等。在管理員的powershell中執行:npm i -g windows-build-tools
這是個漫長的過程,需要下載很長時間。裝好后會發現開始菜單多了這些東西:

在出現上述內容之前,不要動你的命令行!即使它很卡,因為下載上述工具需要幾個 G ,頗費一番工夫。運氣好的話安裝完成,否則就真的卡住了:這不是電腦的問題,是上面安裝腳本的bug。解決方法參照這里:npm安裝windows-build-tools時卡在Successfully installed Python 2.7 。大致是強行給一個日志文件,這是管用的。
然后參考這里:node-ffi從入門到放棄(安裝篇) ,大致要做這些事:
- 設置python路徑,特別是如果電腦上有 python3 更要做這件事:
npm config set PYTHON %PYTHON2%
- 設置 vs 版本:
npm config set msvs_version 2017
- 安裝 node-gyp :
npm i -g node-gyp
如果還報錯,可以清理一下緩存,或者重啟一下計算機。
如果報錯,可以試着裝一下 node-gyp 編譯包:npx node-gyp install --dist-url=https://npm.taobao.org/mirrors/node
(如果提示找不到 npx
,則去掉前面的 npx
)。
然后才能開心地引入 node-ffi-napi
: 在當前文件夾執行 npm install ffi-napi
。
這樣終於做完准備工作了!
柳暗花明
現在終於可以做一個測試。先測試下一個廣為流傳的示例:
// 引入 ffi 包
const ffi = require("ffi-napi");
function TEXT(text) {
return Buffer.from(`${text}\0`, "ucs2");
}
// 使用 ffi.Library 定義函數庫,第一個參數傳所在的 dll ,第二個參數傳需要的函數。
// 函數后面分別是輸出和輸入類型
const user32 = new ffi.Library("user32", {
MessageBoxW: ["int32", ["int32", "string", "string", "int32"]],
});
// 調用,會彈出一個窗口
const OK_or_Cancel = user32.MessageBoxW(0, TEXT("Hello from Node.js!"), TEXT("Hello, World!"), 1);
console.log(OK_or_Cancel);
運行,果然彈出一個提示窗口:
測試正常,可以進行下一步的操作了。
回到原來的代碼,再貼一次:
hWnd := winGetID("A")
DefaultIMEWnd := DllCall("imm32\ImmGetDefaultIMEWnd", "Uint", hWnd , "Uint")
SendMessage(
0x283, ; Message : WM_IME_CONTROL
0x002, ; wParam : IMC_SETCONVERSIONMODE
1025, ; lParam : (1025-CN; 0-EN)
, ; Control : (Window)
; Retrieves the default window handle to the IME class.
"ahk_id " DefaultIMEWnd
)
顯然我們需要引入 SendMessage
,可是並沒有。查文檔后使用 SendMessageW
替換。此函數原型為:
LRESULT SendMessageW (
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM IParam
)
可以不管這些奇怪的類型,四個參數分別給 long
int32
int32
int32
,返回值給 int32
。給出:
const ffi = require('ffi-napi');
const user32 = new ffi.Library("user32", {
"SendMessageW": ['int32', ['long', 'int32', 'int32', 'int32']],
});
然后分別填入這四個參數。暫時跳過第一個,剩下三個給 0x283
,0x002
,1025
。這就是與上述 AHK 的代碼相對應的 Msg
wParam
lParam
。注意,最后一個參數給 1025
對應中文狀態,0
對應英文狀態。
當然,如果 wParam
給 0x001
,就是檢測當前輸入法狀態,可以接收一下返回值。此時 lParam
可以不給。
然后回來看第一個 hWnd
,要填入一個句柄。當然是要填輸入法的句柄了;這個句柄從 AHK 的代碼中可以看出,使用 imm32.dll
中的 ImmGetDefaultIMEWnd
獲取,於是另定義一個 ffi.Library
:
const imm = new ffi.Library("imm32" ,{
"ImmGetDefaultIMEWnd": ["int32" , ["int32"]]
});
上述 AHK 代碼中的 DllCall
函數的參數提示我們,此 ImmGetDefaultIMEWnd
返回一個 UInt
,且需要接受 UInt
型的 hWnd
。這與上述函數庫中的引入是一致的(當然這里簡單粗暴,都給了 int32
的型)。
而原來程序中的 winGetID("A")
得到的句柄是當前窗口的句柄,於是查閱資料,在 WinAPI 中可以使用 GetForegroundWindow()
函數,其同樣存在於 user32.dll
,用類似上述的方法引用。
然后可以調用這些函數了。
這樣補完全部代碼,實現一個英文切中文的程序:
const ffi = require('ffi-napi');
const user32 = new ffi.Library("user32", {
"SendMessageW": ['int32', ['long', 'int32', 'int32', 'int32']],
"GetForegroundWindow":["int32",[]]
});
const imm = new ffi.Library("imm32" ,{
"ImmGetDefaultIMEWnd": ["int32" , ["int32"]]
});
var hwnd = user32.GetForegroundWindow()
var defaultIMEWnd = imm.ImmGetDefaultIMEWnd(hwnd)
user32.SendMessageW(defaultIMEWnd,0x283,0x002,1025);
在 Node.js
中跑一下,效果如下,注意輸入法狀態:
成功!🎉
編寫插件
這樣就可以寫一個 VS Code 插件,仍是和 上一篇文章 一樣,拿到當前的上下文標記之后自動切換即可。於是可以在寫 LaTeX
的時候少按幾次 shift 了!
插件地址:Shift IM for Math
GitHub地址,歡迎來star⭐~

原ahk版本的地址,歡迎來交流~

原文鏈接:https://www.cnblogs.com/yf-zhao/p/16032543.html 轉載請注明出處!