原生 JS 實現 VS Code 自動切換輸入法狀態!這次沒有AHK


上一篇文章:使用 AHK 在 VS Code 中根據上下文自動切換輸入法狀態 給出一個使用 ahk 在 VSCode 自動切換輸入法的方法。不過這個方法實際上很蹩腳,一點都不優(zhuang)雅(bi)。一直想能不能直接使用 js 實現這個,但是 js 大多數是用來搞前端,對 winAPI 沒什么支持,因此頗費了一番周折。

直到發現一個可以用來調用 winAPI 的包叫做 node-ffi ,以及它的 升級版 node-ffi-napi,才算是拿到了這個接口。測試性能比原來的好了十倍,使用 AHK 大概需要 20毫秒,現在約1-3 毫秒就能完成切換。

效果和原來大同小異,但是這次 直接安裝插件即可,無需其他操作

image

插件已經發布到插件市場: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 參數給 10250 ,分別對應中英文

接下來問題,就是如何將這一流程搬到 JS 上去。搜索一番后,找到了這個包:node-ffi ,可惜已經停更了,沒法在新 node 上跑。於是改用 node-ffi-napi 包。

安裝問題

以下默認裝好了 Node.jsnpm

話不多說,直接 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);

運行,果然彈出一個提示窗口:

image

測試正常,可以進行下一步的操作了。

回到原來的代碼,再貼一次:

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']],
});

然后分別填入這四個參數。暫時跳過第一個,剩下三個給 0x2830x0021025 。這就是與上述 AHK 的代碼相對應的 Msg wParam lParam。注意,最后一個參數給 1025 對應中文狀態,0 對應英文狀態。

當然,如果 wParam0x001 ,就是檢測當前輸入法狀態,可以接收一下返回值。此時 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 中跑一下,效果如下,注意輸入法狀態:

image

成功!🎉

編寫插件

這樣就可以寫一個 VS Code 插件,仍是和 上一篇文章 一樣,拿到當前的上下文標記之后自動切換即可。於是可以在寫 LaTeX 的時候少按幾次 shift 了!

插件地址:Shift IM for Math

GitHub地址,歡迎來star⭐~

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


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM