在三种键盘语言间切换
Switch between three keyboard languages

原始链接: https://mnaoumov.dev/posts/2023-12-17-switch-between-three-keyboard-languages/

使用 Windows 默认的循环切换方式管理三种键盘语言(英语、俄语和乌克兰语)效率低下。为了寻找直接跳转到特定语言的方法,作者开发了一个自定义的 AutoHotkey (v2) 脚本。 该脚本通过调用 Windows API,无论当前处于何种选择状态,都能以编程方式设置活动的输入语言。最初,作者尝试将单独的按键(左 Ctrl、右 Ctrl、右 Alt)映射到特定语言,但遇到了 AltGr 功能带来的复杂问题以及与现有键盘快捷键潜在的冲突。 为了解决这一问题,作者改进了方案,将 **Caps Lock** 用作修饰键。现在,通过按下 **Caps Lock + 1、2 或 3**,用户可以分别立即切换到英语、俄语或乌克兰语。这种方法既保留了按键的原始功能,又为多语言工作流提供了可靠且易于形成肌肉记忆的快捷方式。提供的脚本包含了详细的文档和 WinAPI 参考,供那些希望实现类似自定义解决方案的人参考。

Hacker News 最新 | 过往 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 在三种键盘语言间切换 (mnaoumov.dev) 4 点,由 ankitg12 发布于 1 小时前 | 隐藏 | 过往 | 收藏 | 讨论 帮助 准则 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Hi folks.

On the regular basis I use three keyboards: English, Russian and Ukrainian. I need them quite often and I need a quick way to switch between them. Default Alt + Shift and Win + Space are not good enough. Cyclical changes don’t work well sometimes and I have to click more and look in the notification error for the language label.

There are many tools for switching languages such as Punto Switcher but all that I tried work fine with two languages and don’t suggest anything nice for three language users. There are some advices how to use three languages but I didn’t find them practical.

My need is to be able to quickly switch to the desired language regardless of the current language selected.

I came up with the following idea:

  • Left Control to switch to English language
  • Right Control to switch to Russian language
  • Right Alt to switch to Ukrainian language

But also I need to keep the default behavior of those buttons and the shortcuts using those keys.

I would like to share the solution I came up with:

I built a script for AutoHotkey

#Requires AutoHotKey v2.0
#SingleInstance Force
#Warn All, MsgBox
#UseHook

; Based on https://www.autohotkey.com/boards/viewtopic.php?f=6&t=18519

setDefaultKeyboard(localeId) {
    ; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
    static SPI_SETDEFAULTINPUTLANG := 0x005A

    ; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
    static SPIF_SENDWININICHANGE := 2

    ; https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-inputlangchangerequest
    static WM_INPUTLANGCHANGEREQUEST := 0x0050

    ; https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-inputlangchange
    static WM_INPUTLANGCHANGE := 0x0051

    ; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadkeyboardlayouta
    static KLF_ACTIVATE := 0x00000001

    ; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadkeyboardlayouta
    pwszKLID := Format("{:08x}", localeId)
    Flags := KLF_ACTIVATE
    keyboardLayout := DllCall("LoadKeyboardLayout", "Str", pwszKLID, "Int", Flags)

    ; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
    uiAction := SPI_SETDEFAULTINPUTLANG
    uiParam := 0
    pvParam := keyboardLayout
    fWinIni := SPIF_SENDWININICHANGE
    DllCall("SystemParametersInfo", "UInt", uiAction, "UInt", uiParam, "UPtr", pvParam, "UInt", fWinIni)

    windowIds := WinGetList()

    for windowId in windowIds {
        try {
            PostMessage(WM_INPUTLANGCHANGEREQUEST, 0, keyboardLayout, , "ahk_id" . windowId)
            PostMessage(WM_INPUTLANGCHANGE, 0, keyboardLayout, , "ahk_id" . windowId)
        } catch {
            ; Skip access denied windows
        }
    }
}

; https://stackoverflow.com/questions/14701095/how-to-get-keyboard-layout-name-from-a-keyboard-layout-identifier
; https://learn.microsoft.com/en-us/globalization/windows-keyboard-layouts

localeId_English_USA := 0x0409
localeId_Russian_Russia := 0x0419
localeId_Ukrainian_Ehnanced := 0x20422

~LControl: {
    global isAltGr := false
    Sleep 100
    if (isAltGr) {
        return
    }

    SetDefaultKeyboard(localeId_English_USA)
}

~RControl: SetDefaultKeyboard(localeId_Russian_Russia)

~RAlt: SetDefaultKeyboard(localeId_Ukrainian_Ehnanced)

~LControl & RAlt: {
    global isAltGr := true
    SetDefaultKeyboard(localeId_Ukrainian_Ehnanced)
}

Gotchas here:

  1. I’ve added links to every WinAPI function and constant for better maintainability. Surprisingly most of WinAPI examples I see on the Internet are written very poorly.
  2. Tilde prefix ~ (https://www.autohotkey.com/docs/v2/Hotkeys.htm)

    When the hotkey fires, its key’s native function will not be blocked (hidden from the system).

  3. Physical Right Alt button in some keyboard layouts (in my case, both Russian and Ukrainian) act as AltGr, which is an equivalent of LControl & RAlt. Therefore it requires special check to distinguish LControl & RAlt (as a physical button of Right Alt) from fair Left Control. As I figured out, it triggers LControl handler first and then LControl & RAlt, that’s why I had to add a global variable isAltGr and a small delay to handle this difference.

Stay tuned!

UPD: After some time, I found the selected shortcuts not so pleasant to use, then I realized, there is a key that I used only a few time in my life: Caps Lock, so I decided to utilize it. Now Caps Lock + 1 – English, Caps Lock + 2 – Russian, Caps Lock + 3 – Ukrainian. It’s a bit unusual to have Caps Lock as a modifier, so it took my muscle memory a bit of time to kick in, but now I am happily using it.

The changes in the script I put before

CapsLock & 1:: SetDefaultKeyboard(localeId_English_USA)
CapsLock & 2:: SetDefaultKeyboard(localeId_Russian_Russia)
CapsLock & 3:: SetDefaultKeyboard(localeId_Ukrainian_Ehnanced)
联系我们 contact @ memedata.com