user1651105
user1651105

Reputation: 1807

How to detect keyboard input code page

I need to detect the code page the keyboard input is using while user is entering data into application fields.

I tried to use System.Text.Encoding.Default.CodePage; but it gives the code page of what is configured in regional settings.

Then i thought Console.InputEncoding.CodePage; could work, but still, code page is the same as with the above example.

The problem is, user may have a Cyrilic (Windows-1251) code page because of the regional settings, but he may wish to use a different input language. The data user enters is then saved to a file, and that file can be opened in a system which has a different regional settings. Along with text, i am saving code page number, so my app can load the file and display the text correctly. I cannot use Unicode for cross compatibility with a different app which does not support unicode.

Upvotes: 2

Views: 1241

Answers (1)

Cody Gray
Cody Gray

Reputation: 244722

Disclaimer: I'm not an expert on the globalization side of the .NET Framework, so it might wrap equivalent functionality somewhere or another. If so, and you can locate it, great—use that instead. I thought maybe Globalization.CultureInfo.CurrentCulture would return the information we're looking for, but alas it does not; instead, it appears to return the system default culture even if the keyboard layout is changed. I stopped investigating. The described approach is guaranteed to work, albeit with a little extra code.

To determine the code page associated with a particular keyboard layout, you can call the Win32 GetLocaleInfo function, specifying the language ID associated with the current keyboard layout. You request this using the LOCALE_IDEFAULTANSICODEPAGE constant.

To call these functions from a .NET application, you will need to use P/Invoke. You'll also need to define function equivalents for some essential macros.

const int LOCALE_IDEFAULTANSICODEPAGE = 0x1004;
const int LOCALE_RETURN_NUMBER        = 0x20000000;
const int SORT_DEFAULT                = 0x0;

static int LOWORD(IntPtr val)
{
   return (unchecked((int)(long)val)) & 0xFFFF;
}

static int MAKELCID(int languageID, int sortID)
{
   return ((0xFFFF & languageID) | (((0x000F) & sortID) << 16));
}

static int MAKELANGID(int primaryLang, int subLang)
{
   return ((((ushort)(subLang)) << 10) | (ushort)(primaryLang));
}

[DllImport("kernel32.dll", SetLastError = true)]
static extern int GetLocaleInfo(int locale,
                                 int lcType,
                                 out uint lpLCData,
                                 int cchData);

[DllImport("user32.dll")]
static extern IntPtr GetKeyboardLayout(uint idThread);

Use it like this:

// Get the keyboard layout for the current thread.
IntPtr keybdLayout = GetKeyboardLayout(0);

// Extract the language ID from it, contained in its low-order word.
int langID = LOWORD(keybdLayout);

// Call the GetLocaleInfo function to retrieve the default ANSI code page
// associated with that language ID.
uint codePage = 0;
GetLocaleInfo(MAKELCID(langID, SORT_DEFAULT),
               LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
               out codePage,
               Marshal.SizeOf(codePage));

When tested with a US English keyboard layout, codePage is 1252. After switching to a Greek keyboard layout, codePage is 1253. Likewise, Turkish returns 1254, and the various cyrillic languages return 1251. Exactly as documented.

It is worth noting that, in the linked documentation, these API functions are indicated as having been superseded. With modern versions of Windows, Microsoft has moved to named locales, first because they were out of room for numeric IDs, and second to enable support for custom locales. But you will need to use the old functions for what you're doing. Modern Windows applications don't use ANSI code pages, either.

However, you do need to be aware of this fact, because it may come back to bite you. There are keyboard layouts that do not have an associated ANSI code page. For these, only Unicode can be used. The above code will return CP_ACP (which is equivalent to the numeric value 0). Handling that is up to you. You will either need to display an error, or save the file as Unicode (albeit breaking the other application, but complying with user expectations).

Finally, I must point out that if you cache the codePage value, it may become stale since the user can change the keyboard layout at any time. It is probably easiest just not to cache the value, determining it each time you perform a save. But if you want to cache it, you will need to handle the WM_INPUTLANGCHANGE message and update your cached value in response.

Upvotes: 3

Related Questions