0

Question

Why passing the output of GetKeyboardLayout (after formatting it to hex) into LoadKeyboardLayout doesn't work?

The docs for GetKeyboardLayout say (bolded by me):

GetKeyboardLayout

Retrieves the active input locale identifier (formerly called the keyboard layout).

Return value

Type: HKL

The return value is the input locale identifier for the thread. The low word contains a Language Identifier for the input language and the high word contains a device handle to the physical layout of the keyboard.

And the docs for LoadKeyboardLayout say (bolded by me):

LoadKeyboardLayoutA

Loads a new input locale identifier (formerly called the keyboard layout) into the system.

Syntax

HKL LoadKeyboardLayoutA(
  LPCSTR pwszKLID,
  UINT   Flags
);

Parameters

pwszKLID

Type: LPCTSTR

The name of the input locale identifier to load. This name is a string composed of the hexadecimal value of the Language Identifier (low word) and a device identifier (high word). For example, U.S. English has a language identifier of 0x0409, so the primary U.S. English layout is named "00000409". Variants of U.S. English layout (such as the Dvorak layout) are named "00010409", "00020409", and so on.

The way I understand the bolded texts above is that passing the output of GetKeyboardLayout (after formatting it to hex) into LoadKeyboardLayout should just work, but it doesn't.

Example

Let's say that:

  • Your default keyboard language is English US (can be anything else).
  • Your current keyboard language is Russian (can be anything else).

Now, go ahead and run the following AutoHotKey script:

^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
}

Printing getKeyboardLayout's variables gives:

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

Oops!
The fact that resultKL value is English US identifier and not Russian identifier means failure!
Here are the docs again (bolded by me):

If the function succeeds, the return value is the input locale identifier corresponding to the name specified in pwszKLID. If no matching locale is available, the return value is the default language of the system.

The Problem

If we would have passed 00000419 (the identifier listed for Russian here and here) to loadKeyboardLayout instead of passing 04190419, we would have get the desired result.

However, this won't work for other languages' layouts.

For example, let's take Hindi:

  • If Hindi Traditional is the current keyboard layout, getCurrentKeyboardLayout outputs 0xf00c0439. Passing this value (ommiting the 0x prefix) to loadKeyboardLayout wouldn't work, but passing the listed value 00010439 will.
  • If Hindi Phonetic is the current keyboard layout, getCurrentKeyboardLayout outputs 0x4090439. Passing this value (ommiting the 0x prefix) to loadKeyboardLayout wouldn't work, and since Hindi Phoentic doesn't have a listed value, we can't get it from anywhere else.

How can we load these (or other) layouts through LoadKeyboardLayout if it's not willing to work with the full identifier?

I guess I'm missing something fundamental, so please enlighten me.

0

I overlooked the difference between the two cited docs:

While GetKeyboardLayout return-value is the input locale identifier, LoadKeyboardLayout input value should be the name of the input locale identifier.

GetKeyboardLayout returns a variable dubbed an "input locale identifier" of type HKL (which stands for "handle to a keyboard layout"), while LoadKeyboardLayout receives a parameter of type LPCSTR (named pwszKLID, where pwsz stands for a "pointer to wide-character string, zero-terminated" (that's Hungarian notation) and KLID stand for "keyboard layout identifier").

Now, to get a KLID from a HKL, we need to use GetKeyboardLayoutName. Sadly, it doesn't receives a HKL as parameter, but only retrieves the name of the currently active input locale identifier - but we can use this code I've found here:

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)
} 
  • Note: we could actually only use the line with DllCall("GetKeyboardLayoutName", Ptr, &KLID) and change the language manually, but then this line should actually be called twice.

So, keeping with the Russian example, 0x4190419 is the HKL, 00000419 is the KLID, and 04190419 is meaningless.

It worth citing here Michael Kaplan, who was a developer in the Windows International group. He had a post titled "Why are the HKL and KLID of the keyboard different?". He answers:

I actually get this question on a regular basis, believe it or not.

People look at the two numbers, see the similarities, and then start assuming they are the same

The differences are not obvious if you install keyboards via the LoadKeyboardLayout API [...] In that case, the same LCID [locale identifier] is always used, and if the keyboard is one of the many with KLID values like 00000409 or 00000407 then the HKL value will be the same as KLID, further making people think they are the same.

However, there are two times when they can and will be different:

  1. Any time the KLID value is more than just the LANGID -- like 00010439 for the Hindi Traditional keyboard layout or 0003041e for the Thai Pattachote (non-ShiftLock) keyboard layout, the HKL will have a high word of the lower DWORD filled with different information.
  2. [...]

Ok, what about Hindi, then?

Unfortunately, it turns out all that information still doesn't help! Specifically:

  • Calling getKLIDfromHKL(0xf00c0439) (for Hindi Traditional) results in an error in the first call for ActivateKeyboardLayout, I'm not sure why.
  • Calling getKLIDfromHKL(0x4090439) (for Hindi Phonetic) outputs 00000409, which is the KLID for English.
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.