将 GetKeyboardLayout 的输出传递到 LoadKeyboardLayout 不起作用

将 GetKeyboardLayout 的输出传递到 LoadKeyboardLayout 不起作用

问题

GetKeyboardLayout为什么将(格式化为十六进制后)的输出传递到LoadKeyboardLayout不起作用?

文档GetKeyboardLayout说(我加粗的部分):

获取键盘布局

检索活动输入区域标识符(以前称为键盘布局)。

返回值

类型: HKL

返回值是输入区域标识符用于线程。 低字包含语言标识符输入语言和 高位字包含设备句柄键盘的物理布局。

以及LoadKeyboardLayout说(我加粗的部分):

加载键盘布局A

将新的输入法区域标识符(以前称为键盘布局)加载到系统中。

句法

HKL LoadKeyboardLayoutA(
  LPCSTR pwszKLID,
  UINT   Flags
);

参数

pwszKLID

类型:LPCTSTR

输入区域标识符的名称要加载的。此名称是由语言标识符(低位字)和一个设备标识符(高位字)。例如,美国英语的语言标识符为 0x0409,因此主要的美国英语布局被命名为“00000409”。美国英语布局的变体(例如 Dvorak 布局)被命名为“00010409”、“00020409”等等。

我理解上面加粗文字的方式是,将GetKeyboardLayout(格式化为十六进制后)的输出传递给LoadKeyboardLayout应该可以工作,但事实并非如此。

例子

假设:

  • 您的默认键盘语言是美国英语(可以是其他任何语言)。
  • 您当前的键盘语言是俄语(可以是其他任何语言)。

现在,继续运行以下 AutoHotKey 脚本:

^1::run()

run() {
   currentKL := getCurrentKeyboardLayout()
   currentKL_hex := Format("{:#x}", currentKL)
   currentKL_hexsub := Format("{:08}", SubStr(currentKL_hex, 3))
   resultKL := Format("{:#x}", LoadKeyboardLayout(currentKL_hexsub))
}
getCurrentKeyboardLayout() {
   WinGet, winId,, A
   threadId := DllCall("GetWindowThreadProcessId", "uint", winId, "uint", 0)
   inputLocaleIdentifierId := DllCall("GetKeyboardLayout", "uint", threadId, "uint")
   Return inputLocaleIdentifierId
}
loadKeyboardLayout(inputLocaleIdentifierName) {
   KLF_ACTIVATE := 1
   inputLocaleIdentifierId := DllCall("LoadKeyboardLayout", "Str", inputLocaleIdentifierName, "uint", KLF_ACTIVATE)
   Return inputLocaleIdentifierId
}

打印getKeyboardLayout的变量给出:

currentKL: 68748313        <-- Russian identifier,    int, output of GetKeyboardLayout 
currentKL_hex: 0x4190419   <-- Russian identifier,    hex
currentKL_hexsub: 04190419 <-- Russian identifier,    hex, input for LoadKeyboardLayout
resultKL: 0x4090409        <-- English US identifier, hex, output of LoadKeyboardLayout

哎呀!
resultKL是英语美国标识符而不是俄语标识符,这意味着失败!
以下是文档(我加粗了):

如果函数成功,返回值是与 pwszKLID 中指定的名称相对应的输入区域标识符。如果没有可用的匹配语言环境,则返回值为系统的默认语言。

问题

如果我们通过了00000419(俄罗斯列出的标识符这里这里) 而loadKeyboardLayout不是传递04190419,我们就会得到期望的结果。

然而,这对其他语言的布局不起作用。

以印地语为例:

  • 如果当前键盘布局为 Hindi Traditional,则getCurrentKeyboardLayout输出0xf00c0439。将此值(省略前缀0x)传递给loadKeyboardLayout不起作用,但传递标价 00010439将要。
  • 如果当前键盘布局为 Hindi Phonetic,getCurrentKeyboardLayout则输出0x4090439。将此值(省略前缀0x)传递给loadKeyboardLayout不起作用,由于 Hindi Phoentic 没有列出值,因此我们无法从其他地方获取它

LoadKeyboardLayout如果它不愿意使用完整的标识符,我们如何加载这些(或其他)布局?

我想我遗漏了一些基本的东西,所以请启发我。

答案1

我忽略了所引用的两个文档之间的区别:

GetKeyboardLayout返回值是输入语言环境标识符,而LoadKeyboardLayout输入值应该是姓名输入法区域标识符。

GetKeyboardLayout返回一个被称为“输入区域标识符”类型的变量HKL(代表“键盘布局句柄”),同时LoadKeyboardLayout接收一个类型为的参数LPCSTR(名为pwszKLID,其中pwsz代表“指向宽字符串的指针,以零终止”(匈牙利表示法)和KLID代表“键盘布局标识符”)。

KLID现在,要从a 中获取 a HKL,我们需要使用GetKeyboardLayoutName。遗憾的是,它不接收 aHKL作为参数,而只检索当前活动的输入语言环境标识符的名称 - 但我们可以使用我找到的这段代码这里

getKLIDfromHKL(HKL) {
    VarSetCapacity(KLID, 8 * (A_IsUnicode ? 2 : 1))
    priorHKL := DllCall("GetKeyboardLayout", Ptr,DllCall("GetWindowThreadProcessId", Ptr,0, UInt,0, Ptr), Ptr)
    if !DllCall("ActivateKeyboardLayout", Ptr, HKL, UInt,0) || 
       !DllCall("GetKeyboardLayoutName", Ptr, &KLID)
        Return false
    DllCall("ActivateKeyboardLayout", Ptr, priorHKL, UInt,0)
    MsgBox, % StrGet(&KLID)
} 
  • 注意:我们实际上只能使用该行DllCall("GetKeyboardLayoutName", Ptr, &KLID)并手动更改语言,但实际上应该调用该行两次。

因此,按照俄罗斯的例子,0x4190419HKL00000419KLID,并且04190419毫无意义。

这里值得引用一下 Michael Kaplan,他是 Windows 国际组的一名开发人员。他发表了一篇题为“为什么键盘的HKL和KLID不一样?”。 他回答:

不管你信不信,事实上我经常会被问到这个问题。

人们看到这两个数字,发现它们的相似之处,然后开始假设它们是相同的

如果你通过 API 安装键盘,那么差异并不明显 LoadKeyboardLayout[...] 在这种情况下,相同的 LCID [区域设置标识符] 总是被使用,并且如果键盘是具有KLID0000040900000407这样的值的众多键盘之一,那么该HKL值将与 相同KLID,进一步使人们认为它们是相同的。

然而,有两种情况它们可能并且将会有所不同:

  1. 任何时候,KLID值不仅仅是 LANGID——例如00010439 对于印地语传统键盘布局或0003041e泰语 Pattachote(非 ShiftLock)键盘布局,HKL将会有一个低位 DWORD 的高位字,其中包含不同的信息。
  2. [...]

好的,那么印地语怎么样?

不幸的是,事实证明所有这些信息仍然无济于事!具体来说:

  • 调用getKLIDfromHKL(0xf00c0439)(针对传统印地语)会导致第一次调用时出现错误ActivateKeyboardLayout,我不确定为什么。
  • 呼叫getKLIDfromHKL(0x4090439)(印地语语音)输出00000409,这是英语的 KLID。

相关内容