What a tangled web we weave when a KLID from an HKL we must receive

by Michael S. Kaplan, published on 2008/09/29 03:01 -04:00, original URI: http://blogs.msdn.com/b/michkap/archive/2008/09/29/8968315.aspx


For the title to work best, you have to use the pronunciations from Some keyboarding terms for the terms...

In a series of newsgroup posts, Udi Raz asked many related questions:

There are two Bengali layouts on XP.

I am able to load and activate the Bengali layout using :

CString strLCID = _T("00000445");
HKL hNewKeyboard = LoadKeyboardLayout(strLCID,KLF_SUBSTITUTE_OK);
HKL hkl = ActivateKeyboardLayout(hNewKeyboard,0);

or to activate the bengali on a different window using :
::PostMessage( m_foregrounfWnd, WM_INPUTLANGCHANGEREQUEST, 1, 0x04450445 );

the second layout, Bengali inscript does not work.

I obtain the Bengali Inscript by adding it to the language bar, choosing it
and using :
DWORD dwThreadID = ::GetWindowThreadProcessId (foregrounfWnd, &dwProcessID);
HKL focusLangId = ::GetKeyboardLayout(dwThreadID);

The value I got is : focusLangId = 0xF02A0445

Please advice how to load it
Thanks,
Udi Raz



Hi,

The following code does not activate Bengali Inscript but do activate
Benagli (0x00000445) 

 LoadKeyboardLayout(strLCID,KLF_SUBSTITUTE_OK);
::PostMessage( m_foregrounfWnd, WM_INPUTLANGCHANGEREQUEST,
INPUTLANGCHANGE_SYSCHARSET, 0x00010445);

while the code below works well for both Bengali layouts
HKL hNewKeyboard = LoadKeyboardLayout(strLCID,KLF_ACTIVATE);
ActivateKeyboardLayout(hNewKeyboard,KLF_SETFORPROCESS);

I would like to change the language of other application and therefore I am
using PostMessage. It looks like that the postmessage does not call
ActivateKeyboardLayout with KLF_SETFORPROCESS flag.

Please advice

Now the biggest problem here is a misunderstanding I cover in one of my earliest blogs (Some keyboarding terms) -- the difference between a KLID and an HKL.

Now the topic has come up a few times since then, like in Thursday morning wrestling: LCID vs. HKL, Why are the HKL and KLID of the keyboard different?, and many others.

Now the problem here is kind of like the one I discuss in How do I get the @!#$% name of the keyboard?, which is trying to get the KLID when one has the HKL.

This is essentially the same problem -- one has the HKL but one wants to get the KLID.

Now of course solving the converse problem is easy -- a simple call to LoadKeyboardLayout will take a KLID and give you an HKL. And then the only thing you need to do is make sure that you unload it (via an UnloadKeyboardLayout call) if it was not already loaded, a problem I talk about in Getting all you can out of a keyboard layout, Part #2. But that is really very easy.

Now admittedly that problem would have much easier to handle if USER's keyboarding API borrowed a page from the ADVAPI32 registry API with its RegCreateKeyEx/RegOpenKeyEx or KERNEL32's DLLs/Processes/Threads API did with its LoadLibrary/GetModuleHandle -- distinguishing between trying to point to something if it is already around, versus load it first if it's not there.

And life would much easier if that existed, I will admit -- in fact my own life would have been easier when the MSKLC work was going on. But the problem is still easily solvable so there are no real worries there. It is probably time to get over it and stop whining so much. :-)

But as I pointed out in How do I get the @!#$% name of the keyboard?, the other way around is not so easy at all.

The only function that exists to help is GetKeyboardLayoutName. And it will only deal with the current input language in a thread, not the other ones that may be loaded with their own HKL values.

Changing the current HKL (via ActivateKeyboardLayout, for example) is a nightmare of unwanted notification messages and communications that is so awful that even .NET's InputLanguage class saw its developers throwing up their hands and writing their own code to parse the almost-impossible-to-figure-out HKCU keys to find out about loaded input languages, in order to avoid tripping up its own input language events functionality.

It's a freaking clusterfrick, if you know what I mean.

Even if you are willing to live with the unwanted events and such, using the same code How do I get the @!#$% name of the keyboard? uses to only unload what is already loaded, you will won't have everything.

Because a long time ago it was decided that you could put any keyboard under any language. And the HKL's LANGID is based on the language you put the keyboard under, not the actual keyboard layout that the KLID expresses.

In fact the TSF functions are the only way to even add such language/keyboard combinations yourself, though there is a newly documented function (only really available in Vista) that makes this all at least possible (EnumEnabledLayoutOrTip). It returns a bunch of LAYOUTORTIPPROFILE stuctures, which are defined as follows:

typedef struct tagLAYOUTORTIPPROFILE {
    DWORD  dwProfileType;       // InputProcessor or HKL
#define LOTP_INPUTPROCESSOR 1
#define LOTP_KEYBOARDLAYOUT 2
    LANGID langid;              // language id
    CLSID  clsid;               // CLSID of tip
    GUID   guidProfile;         // profile description
    GUID   catid;               // category of tip
    DWORD  dwSubstituteLayout;  // substitute hkl
    DWORD  dwFlags;             // Flags
    WCHAR  szId[MAX_PATH];      // KLID or TIP profile for string
} LAYOUTORTIPPROFILE;

Note that this structure will give you both the LANGID and the KLID, though interestingly not the HKL.

Though now, even armed with EnumEnabledLayoutOrTip, you still need ActivateKeyboardLayout, GetKeyboardLayoutName, LoadKeyboardLayout, and UnloadKeyboardLayout if you really want to get the KLID of a specific HKL, unless you trust that the never-explained-member of LAYOUTORTIPPROFILE member dwSubstituteLayout is a 32-bit HKL (shadows of the 32 bit vs. 64 bit HKLs? problem now, since this function works the same on 64-bit Windows as on 32-bit!).

We'll just leave the actual problem Udi Raz was having, of trying to load anything other than the first, default input language under the same LANGID as the underlying KLID is used as a difficult one (though all of the clues are now here for how to solve the problem in Windows >= Vista and most of the clues are now here for Windows in prior versions.

In my opinion the fact that it is so hard for users to do something in this space ought to be considered a genuine bug, though this is just my opinion and have no proof or belief that the owners of the components would feel the same way.

An upcoming blog later this week will clear up the loose ends and make this all a bit easier to work out the solution here.

Perhaps some of this will even be coded up in a future blog beyond that, too.

And I still have bug to explain as well -- this will be in a blog coming up tomorrow or the next day...


This blog brought to you by(U+0990, aka BENGALI LETTER AI)


# John Cowan on 29 Sep 2008 12:04 PM:

As a result of a misspent youth reading too much Heinlein, HKL to me will always stand for "Hong Kong Luna".

# Michael S. Kaplan on 29 Sep 2008 3:30 PM:

There is no such thing as too much Heinlein.

Too much L. Ron Hubbard? Easy. But Heinlein? Never. :-)

# KC Teo on 12 Mar 2009 5:54 AM:

Greeting.  

Just wonder has any one tried calling EnumEnabledLayoutOrTip following what suggested on the MSDN website, ie. using LoadLibrary and GetProcAddress ?  I tried and it crashed my program with error message: 0xC0000005: Access violation writing location <memory location>.  

To make sure I am doing the right thing, I have tried using the same approach to call QueryLayoutOrTipString which belongs to the same Input.dll, that works fine.

Does this some how indicate that the parameters of EnumEnabledLayoutOrTip documented on the MSDN website are inaccurate ?  Thanks.

# FRG on 15 Feb 2010 7:05 AM:

My comment is quite outdated, but since there are no much information about Vista/W7's TSF, I'll hope it'll help someone:

1. EnumEnabledLayoutOrTip() is misdocumented. The correct prototype is  

UINT EnumEnabledLayoutOrTip(LPCWSTR pszUserReg, LPCWSTR pszSystemReg, LPCWSTR pszSoftwareReg, LAYOUTORTIPPROFILE *pLayoutOrTipProfile, UINT uBufLength);

2. There is a very handy (but undocumented) function in input.dll :

HRESULT GetLayoutDescription (LPWSTR szId, LPCWSTR pszName, LPUINT uBufLength, DWORD dwFlags) that returns a human-readable IME description (like "English(US) - Dvorak"). Combining both together, it's easy to get a list of all active IMEs installed.


referenced by

2012/08/28 'If it is easy, then there are samples. And if it's very easy, everyone is writing them.'

2008/10/01 What do you get when you put a Hebrew on top of a Russian? (aka What lies beneath can bite you on the ass)

go to newer or older post, or back to index or month or day