5

This is a continuation from What is the correct, modern way to handle arbitrary text input in a custom control on Windows? WM_CHAR? IMM? TSF?.

So after experimenting with a non-IME layout (US English), a non-TSF IME (the Japanese FAKEIME from the Windows XP DDK), and a TSF text service (anything that comes with Windows 7), it appears that if the active input processor profile is not a TSF text service (that is, it is a TF_PROFILETYPE_KEYBOARDLAYOUT), I'll still have to handle keystrokes and WM_CHAR messages to do text input.

My problem is that my architecture needs a way to be told that it can ignore the current key message because it was translated into a text input message. It does not care whether this happens before or after the translation; it just needs to know that such a translation will or has happened. Or in pseudocode terms:

// if I can suppress WM_CHAR generation and synthesize it myself (including if the translation is just dead keys)
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
    if (WillTranslateMessage())
        InsertChar(GenerateEquivalentChar());
    else
        HandleRawKeyEvent();
    break;

// if I can know if a WM_CHAR was generated (or will be generated; for instance, in the case of dead keys)
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
    if (!DidTranslateMessage())
        HandleRawKeyEvent();
    break;
case WM_CHAR:
case WM_SYSCHAR:
    InsertChar(wParam);
    break;

The standard way of handling text input, either from a keyboard or through a non-TSF IME, is to let TranslateMessage() do the WM_KEYDOWN-to-WM_CHAR translation. However, there's a problem: MSDN says

If the message is WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, or WM_SYSKEYUP, the return value is nonzero, regardless of the translation.

which means that I cannot use it to determine if a translation has occurred.

After reading some Michael Kaplan blog posts, I figured I could use ToUnicode() or ToUnicodeEx() to do the conversion myself, passing in the state array from GetKeyboardState(). The wine source code seems to agree, but it has two special cases that I'm not sure if they are wine-specific or need to be done on real Windows as well:

  • VK_PACKET — generates a WM_CHAR directly out of the message's LPARAM
  • VK_PROCESS — calls a function ImmTranslateMessage(), which seems to either be a wine-specific function or an undocumented imm32.dll function; I can't tell which is true

And wine also does nothing with WM_KEYUP and WM_SYSKEYUP; again, I don't know if this is true for wine only.

But do I even need to worry about these cases in a program that uses TSF? And if I do, what's the "official" way to do so? And even then, what would I do on WM_KEYUP/WM_SYSKEYUP; do I need to send those to ToUnicode() too? Do I even need to catch WM_KEYUPs in my windows specially if there was a WM_CHAR?

Or am I missing something that is not in any of the MSDN TSF samples that will allow me to just have TSF take care of the TF_PROFILETYPE_KEYBOARDLAYOUT processors? I thought TSF did transparent IME passthrough, but my experiment with the FAKEIME sample showed otherwise...? I see both Firefox and Chromium also check for TF_PROFILETYPE_KEYBOARDLAYOUT and even use ImmGetIMEFileName() to see if the keyboard layout is backed by an IME or not, but I don't know if they actually take care of input themselves in these cases...

My minimum version right now is Windows 7.

Thanks.

UPDATE The original version of this question included needing to know about associated WM_KEYUPs; on second look through my equivalent code on other platforms this won't be necessary after all, except for the details of TranslateMessage(); I've adjusted the question accordingly. (On OS X you don't even give key-release events to the text input system; on GTK+ you do but it seems keypresses that insert characters don't bother with releases and so they don't get handled anyway, at least for the input methods I've tried (there could be some that do...).) That being said, if I missed something, I added another sub-question.

Community
  • 1
  • 1
andlabs
  • 11,290
  • 1
  • 31
  • 52
  • Why are you handling key up/down events as opposed to char events? – Eric Brown Jan 03 '17 at 18:52
  • @EricBrown because I would still need to handle things like arrow key navigation and keyboard-driven selection. Or is there another way to do that that I'm missing? Sorry for repeatedly pestering you with small followup questions, by the way. – andlabs Jan 03 '17 at 19:25

1 Answers1

2

In general, it's not a good idea to try to duplicate Windows internals. It's tedious, error-prone, and likely to change without notice.

The edit controls that I have source access to pick off arrow keys (and other specific keys) in the WM_KEYDOWN handler and pass everything else off to the default handler, which will (eventually) generate WM_CHAR or TSF input calls (if your control supports TSF, which it should).

You would still need WM_CHAR in the case where there is no TSF handler involved. However, you can always have your WM_CHAR handler call your ITextStoreACP::InsertTextAtSelection method.

Eric Brown
  • 13,774
  • 7
  • 30
  • 71
  • All right. I do use TSF, and so do the sample tsfpad apps; I'm just not sure why I would still need WM_CHAR in both cases. Oh well, handling that shouldn't be too much extra work. So I guess my custom editor would have to just know that a key would never contribute to text input? (For what it's worth, all the other OSs I need to support do things the other way around: handle text first, then handle whatever keys remain; which is why I ask this question. I'm not sure if that point got lost in the shuffle when I asked the original question.) Thanks in the meantime! – andlabs Jan 03 '17 at 22:24
  • 1
    @andlabs Not sure about text input in other OSes, but in Windows, you can have an arbitrary number of WM_KEYDOWNs before the WM_CHAR. (Consider composing keys for non-IME languages, or ALT-numpad entry of Unicode characters in English.) As it happens, arrow keys are never composing, so it's safe to pick those off early. – Eric Brown Jan 05 '17 at 04:28
  • Right. Other OSs let you do this too; it's just that you have to give them the keystrokes, not the other way around. (You create a proxy object to the input method manager, which I assume is dynamically loaded into the process, and then pass the key event to them as the very first step in your key event handler. They return a boolean indicating whether they handled the keystroke, returning true even if it is just the beginning of a longer composition.) I'm probably just overthinking this too hard and maybe I should just assume non-typewriter keys are safe in general... – andlabs Jan 05 '17 at 21:28
  • @andlabs That's *kinda* how Windows works; the proxy to the input method manager is named TranslateMessage. :) TranslateMessage doesn't return when it's done translating; it just posts a WM_CHAR with the finished character (or it sends everything off to TSF). – Eric Brown Jan 05 '17 at 22:25
  • Of course, but then that circles back to my original question of how to know whether `TranslateMessage()` resulted in a translation, even if it's just one that sets internal state in an IME for a future `WM_CHAR`. I have probably been thinking too hard about this; sorry for the repetitive trouble! ^^; For now I'll just note that typewriter side keys (and possibly numpad keys?) should be ignored and let `WM_CHAR` come when it comes. Until then, thanks! – andlabs Jan 06 '17 at 22:19
  • Oh and I just noticed your last paragraph in the current version of the answer, which does confirm that yes, I was wrong in assuming TSF took over from WM_CHAR. Thanks for that confirmation! – andlabs Jan 06 '17 at 22:20
  • @andlabs I also assumed that if you wrote a TSF-aware application, you wouldn't need to handle `WM_CHAR` (I imagined TSF would call `ITextStoreACP2::InsertTextAtSelection` when appropriate). – Mark Ingram Feb 06 '19 at 14:24
  • I don't know how to respond to that comment, but looking at this again I should have brought up the equivalent functions to what I was looking for on other platforms (though that probably wouldn't change the answer) – andlabs Feb 06 '19 at 15:24