IME Basics for Developers
The advent of Unicode has been a huge boon for developers, enabling applications to run on computers throughout the world with little or no effort on the part of the developer. However, there are several quirks in East Asian operating systems that can make applications developed in the west frustrating or difficult to use when installed on an East Asian computer. This post looks at the most basic issues that every application developer should know. Although I focus on Japanese and Windows in this post, many of the issues are equally applicable to Chinese and Korean, as well as other operating systems.
Input Method Editor (IME)
Because of the complex character sets of the East Asian languages, text needs to be entered through a multi-stage interactive process, which is handled by an OS component called the IME. (I’ve written an article detailing how the Japanese IME works as a bit of a background for people who are interested). Although the OS does it’s best to conceal the operation of the IME from the running applications, the fact that the IME needs to intercept keystrokes and display it’s own UI to the user can create problems for some applications (including web applications) in some situations. Beginning with Windows 98/2000, a system module called the Input Method Manager (imm32.dll) was introduced that offers a common interface that can be used to resolve many IME-related problems.
Disabling the Entire IME
For many games and other applications that do not use text input, the IME is a liability. First, let’s take a look at a screenshot from my favorite third-person shooter, Dead Space (click the image to enlarge):
The text across the top of the screen is the intermediate composition string that appears because the IME is performing romaji-kana conversion on the keyboard input that is controlling the player character. What the screenshot fails to capture is the annoying flickering as the IME and game continually compete to redraw the same section of the screen. It’s a bit like one of those gyrating banner ads that claim that you’re the winning millionth visitor.
Why does this happen? This is the result of an IME feature called the floating conversion window. Although the IME normally performs conversion in-place in the textbox or other control that has the keyboard focus, if the control that has the focus does not have a text input area (or there are no controls), the IME instead displays the floating conversion window temporarily to handle character conversion. A great example of how this feature is intended to work can be seen in Windows Explorer, where you can jump directly to the first file or folder starting with a particular letter by pressing that key. The floating conversion window offers similar functionality to East Asian users.
In applications that are not IME aware, IME activation is fully controlled by the user. In a game, this means that the IME activates if the user bumps any of the IME keys on the keyboard. In something like a first person shooter, it’s very easy to bump one of these when the action suddenly steps up a notch, which is exactly the worst moment to have the screen suddenly filling up with garbage. The problem is also exacerbated by the layout of the Japanese keyboard:
The keys highlighted in red are IME related, and turn on the IME if pressed. The key highlighted in green is actually the space bar, although ever since Microsoft managed to con manufacturers into adding the two Windows logo keys to the standard 109 key Japanese keyboard, it has become more like a space key. As a curiosity, this particular keyboard still employs a wire under the space bar despite the space bar actually being narrower than the shift key:
Solution
The solution to this problem is quite simple. Disable all IME functionality in your application. On Windows 98/2000 and later, this is handled through the appropriately named ImmDisableIME function, which has to be called before your application creates any windows. Because this function is only available if East Asian support is installed, we end up with something like this:
typedef BOOL (WINAPI *pfnImmDisableIME)(DWORD);
void DisableIME() {
HMODULE hImm32;
pfnImmDisableIME pImmDisableIME;
hImm32 = LoadLibrary("imm32.dll");
if (NULL == hImm32) return; // No East Asian support
pImmDisableIME = (pfnImmDisableIME) GetProcAddress(hImm32, "ImmDisableIME");
if (NULL == pImmDisableIME)
{
FreeLibrary(hImm32);
return;
}
pImmDisableIME(-1); // -1 means disable IME in all threads of current process
FreeLibrary(hImm32);
}
I tested for this bug in my entire game collection, with the result that Morrowind, Fallout 3, Assassin’s Creed, and Dead Space all failed. Only Half-Life 2 correctly disables the IME. It is important to note that this measure is not limited to games. Any app that can only handle 7-bit ASCII input should disable the IME. An example of an app that handles this correctly is the calculator app that comes with Windows.
IME Contexts
One of the key things to note about the above example is that disabling the IME in your own process does not disable the IME in other processes. The reason for this is that Windows creates something called an IME Context for each new process that is created, and it is the IME Context that maintains the IME settings that are used when the corresponding app is active. As an example, imagine a Japanese developer who is running Visual Studio and Thunderbird. They turn the IME off in Visual Studio because code is written in 7-bit ASCII, but they turn the IME on in Thunderbird so that they can write their email in Japanese. Because separate IME Contexts were allocated to both Visual Studio and Thunderbird when they were launched, the IME is able to automatically turn Japanese input on or off as the user switches between the two applications.
Most developers do not need to handle IME Contexts explicitly because a default IME Context is automatically allocated to each process by Windows. However, the exception occurs when a single process opens multiple windows that operate as separate entities. Probably the best example of this is the web browser, where individual windows or tabs can be running completely independent tasks. Windows users are used to the IME Context paradigm (even if they are not consciously aware of it), so you really need to respect this system if you are developing for Windows. The latest versions of Firefox (3.5.3), Chrome (3.0.195.21), and Opera (10.00) all get this wrong. IE6, 7, and 8 all work correctly. This is one of the reasons why Firefox, Chrome and Opera have such poor adoption rates in Asia. (I found this interesting article about browser share in China)
Solution
The main API calls are:
HIMC ImmCreateContext(void); Create a new HIMC for each new window.
HIMC ImmAssociateContext(HWND hWnd, HIMC hIMC); Call this on every control you create in each new window.
BOOL ImmDestroyContext(HIMC hIMC); - Destroy the context when you are done
Implementation is simple. For each window or tab, define an HIMC variable to store the context handle. Upon creating each window or tab, call ImmCreateContext to allocate a new context. Every time you create a new control, call ImmAssociateContext to associate the control with the context for that window or tab. Finally, after you have closed the window or tab, call ImmDestroyContext to delete the context again. It is important, however, that all of the controls associated with the context are destroyed before the context is destroyed.
One of the important things to note about the IME Context concept is that it is a Windows-only thing. Unix and Macintosh treat the IME state as a system-wide property, kind of like part of the keyboard. However, the simple fact that there is a difference creates problems for OS-agnostic web apps, as we will see below.
Disabling the IME in Individual Controls
Even in Unicode-aware applications that have been designed to accept any kind of text input, there are still situations where the IME should be disabled for certain controls. The most obvious example of this is the password field. Since the IME operates interactively and relies on prompting the user to select from candidate lists, it would be impossible to conceal the input characters. Of course, password fields are a well-known case, and so the OS or web browser automatically disables the IME for password controls.
However, there are many more subtle examples, such as alphanumeric text fields or custom controls that accept the focus but do not accept text. Let’s take a numeric field as an example to see how things work when the IME is enabled. Suppose a Japanese user tabs from a “name” input field where they have just entered their name (and so the IME is in Kanji mode) to an “age” input field asking for their age. Typing in 34 gives them the following candidate list:
Now there are several problems. In this example, the first candidate is the number in full-width digits (U+FF10 to U+FF19). This is what the user will most probably select, in which case you have to perform full-width to half-width conversion on the input text. (This also applies to general alphanumeric input since there are full-width characters for the entire ASCII 7-bit range). The second problem arises if you are trying to use keydown/keypress events to filter out non-numeric key presses. When the IME is active, keydown/keypress/keyup events follow a different model than normal (which I will go into later), which makes most attempts to filter keyboard input disastrous. (Google really sucked at this when they first attempted to enter the Japanese market). The final problem is one of usability. If a field only accepts numbers, you should save your users the hassle of having to go through the conversion process. (For completeness, the last four candidates in the above list are a bit like writing out “thirty four” in English, so you don’t generally need to worry about them).
Solution
In HTML, Microsoft introduced the “ime-mode” style for text input fields in IE5 to allow web apps some control over the IME mode. However, the other browser makers have opposed introducing this into the standard (I suspect this is largely because it is really a Windows-only concept). Firefox introduced the style in Firefox 3, but because of the problems it has on the Mac, you really want to specify different values for Mac and Windows users. IE8 added an alias “-ms-ime-mode”. (Personally, I think the best case would be for the other browser makers to adopt “-ms-ime-mode” as something that only has an effect on Windows platform browsers.) The key values of ime-mode are:
disabled This style disables the IME only for this control. When the user clicks on or navigates to another control, the IME returns to the same state as it was in before entering the control. This is the best mode to use for number-only or alphanumeric-only fields. Some notes: 1) Because the user cannot override this setting, make sure you only use it on fields that will never allow CJK characters. 2) Users can still cut/paste exotic characters into the field. 3) This setting doesn’t work properly on Mac or Linux systems because there is no OS support for this kind of functionality.
inactive This setting turns the IME off, but allows the user to turn it on again if they wish. Although you should avoid using this setting on Windows (unless you really know what you are doing), on the Mac you should use “inactive” as a replacement for the buggy “disabled” style, particularly because this is more inline with the Mac way of handling Japanese input.
For native Windows applications, the easiest way to control the IME is to use the .NET or similar framework that offers an IMEMode property on controls. (Just set IMEMode=disabled on controls that required only 7-bit ASCII input). Alternatively, you can disable the IME for a single control using the following Win32 API function:
HIMC ImmAssociateContext(HWND hWnd, HIMC hIMC);
Simply specify the HWND to the control in the first parameter, and NULL in the second parameter (NULL disables the IME for that control). As with the ImmDisableIME function, you need to make sure that the imm32.dll module exists before calling the function, or you may run into problems on systems without East Asian support.
Windows Events
Because the IME context is maintained separately within each individual process, the system-wide language bar that displays the IME status needs to communicate with the IME part of your application to keep the language bar icons up to date. This is achieved through the named Windows messages. Named messages are messages in the range 0xC000 through to 0xFFFF. It is essential that you correctly call DispatchMessage on these messages as you receive them in your message loop, or the language bar will stop working.
For those who are interested, the Chinese, Japanese, and Taiwanese language bars are shown below:
Keydown/Keypress/Keyup
Since the IME is intercepting keystrokes to hold a conversation with the user, the keydown/keypress/keyup events take on a slightly new meaning. However, the processing that takes place depends on the particular control that you are using. The most important point to note is that you must not attempt to perform validation on text that is in the middle of conversion. Doing so leads to a terrible end user experience.
Win32 Edit Control
When the IME is actively intercepting keystrokes, it replaces the key value in the WM_KEYDOWN message with the value VK_PROCESSKEY (229) to indicate that a key was pressed, but has been processed by the IME. There is also no corresponding WM_CHAR message (which corresponds to the KeyPress event). However, the WM_KEYUP message is sent with the key code of the correct key. This process is repeated as the user interacts with the IME to select the text that they want to input. When they finally accept the text, your application receives a WM_CHAR message for each character in the selected text. You can look for WM_KEYUP messages where the keycode is 13 (the enter key) to indicate that the user has finished conversion.
RichTextbox
The Win32 Edit control and the NET Framework TextBox control actually don’t interoperate with the IME very nicely, so if you can you should use the RickTextbox control instead. Although the KeyUp and KeyDown events operate the same as in the Edit box, there is no WM_CHAR (KeyPress) message upon the user accepting the input. Instead, the TextChanged event is called when candidate text is accepted.
HTML INPUT TEXT
Internet Explorer and Chrome send 229 for KeyDown and the actual keycode for KeyUp on each key press. There are no KeyPress events. You can therefore trigger on-the-fly validation code on receiving any KeyPress event (non-IME input), and for KeyUp events where the key code is 13 (IME input).
Firefox sends a single 229 key code on KeyDown, and a KeyPress with value 0 when the user first starts interacting with the IME. A final KeyUp event is then sent once the user has finished interacting with the IME. This means that you can use the KeyUp event as a trigger to validate user input regardless of the IME state.
Opera does not send any KeyDown/KeyPress/KeyUp events at all for any text entered via an IME.
CAPS LOCK
The final issue, which is primarily related to games, is the caps lock key. Caps lock on the Japanese keyboard is toggled by holding down shift while tapping the caps lock key. Simply tapping the caps lock key does not toggle the caps lock state. This is an issue in both Morrowind and Fallout 3, which use the caps lock key to toggle running on and off. Unfortunately, both of these games seem to use a mixed bag of methods to test for caps lock, and it’s always a lucky dip to see what key combination will toggle running on or off. Since this is tied to the Japanese keyboard, and not the IME itself, you need to call GetKeyboardLayout and check for a Japanese keyboard, and then maintain your toggle state based on KeyDown events if you want to use caps lock in a game.
Summary
Hopefully this collection of tips will give you some insight into the difficulties of internationalization. If anyone with Mac experience has anything to add, I would love to hear about it in the comments.
Tweet Permalink
Anonymous Coward said,
September 21, 2009 @ 5:27 pm
Really, the green one *has* to be the space bar…
Aaron said,
October 3, 2009 @ 12:53 am
Yes, “green” and “red” are swapped in the keyboard image (the keys highlighted in red are IME-related, and the green one is the space bar/key).
Gatunka said,
October 18, 2009 @ 6:39 pm
Thanks for the heads up. Now fixed.