• 原生 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 转载请注明出处!

  • 相关阅读:
    研发运营一体化(DevOps)能力成熟度模型
    全球主存储市场加速创新,华为连续8年打造中国高科技名片
    算法每日——每日一练
    iTOP2K1000开发板Makefile文件
    搭建zerotier planet服务
    前端提交规范 ESLint + Prettier + husky + lint-staged
    千兆光模块和万兆光模块已经过时了吗?
    hadoop 编写开启关闭集群脚本, hadoop hdfs,yarn开启关闭脚本。傻瓜式hadoop脚本 hadoop(九)
    ctrl+d和ctrl+c的区别
    Haawking ide debug有问题
  • 原文地址:https://www.cnblogs.com/yf-zhao/p/16032543.html