1

I have a problem with changing keyboard layout inside keyboard hook. In this simple code, when pressing 'A' key, it takes a lot time, to change a language, in more complicated cases, application does wrong things..

Application works in tray, therefore I used hooks. Whats wrong with my code? )) Or, maybe there is different way to change keyboard layout, which works with hooks well? Thanks for your answers.

private static bool nextKey = false;

private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) {
    uint tpid = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
    ushort currentLayout = GetKeyboardLayout(tpid);

    if (nCode >= 0 && wParam == (IntPtr) WM_KEYDOWN) {
        if (nextKey) {
            Console.WriteLine("changing to english...");
            PostMessage(GetForegroundWindow(), 0x0050, 0, (int) LoadKeyboardLayout("00000409", 0x00000001));
            nextKey = false;
        }

        int vkCode = Marshal.ReadInt32(lParam);

        if (vkCode == 0x41 && currentLayout == 0x409) { // if language is rus and 'A' pressed
            Console.WriteLine("changing to russian...");
            PostMessage(GetForegroundWindow(), 0x0050, 0, (int) LoadKeyboardLayout("00000419", 0x00000001));
            nextKey = true;
        }
    }
    return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Michael
  • 2,356
  • 3
  • 21
  • 24

1 Answers1

0

Before even trying to fix this code, I have to ask a couple of silly questions:

  1. Why do you need to do this inside of a hook?
  2. Why do you need to do this at all? You can specify a hotkey to choose an input language in the Keyboard control panel (Region and Language → Keyboard and Languages → Change Keyboards → Advanced Key Settings). And whenever you enable multiple input languages, an icon is already placed on your taskbar. You don't need to write your own app to do either of these things.

Now, looking specifically at your code, what you're doing is posting the WM_INPUTLANGCHANGEREQUEST message to the foreground window. But this message is a notification. It informs a program that a user requested to change the input language. It's not designed to allow programs to make requests to other programs to change the input language.

If a program wants to change its own keyboard layout, it calls the ActivateKeyboardLayout function. But it's not necessary to p/invoke this function from a .NET application. The framework already wraps all this up in the InputLanguage class—highly recommended.

Aside from that, other problems inevitably exist in code that you do not show, code that belongs to other applications. The foreground window to which you post the WM_INPUTLANGCHANGEREQUEST message has the option of either accepting the change by passing the message on to DefWindowProc, or rejecting the change by returning 0 in response. If a broken application just returns 0 for all those messages it does not explicitly process, it won't do the right thing. Or if an application has been written to explicitly reject WM_INPUTLANGCHANGEREQUEST requests, it won't do what you're expecting. And so on. You have no control over these things. Remember, WM_INPUTLANGCHANGEREQUEST is just a request.

As far as the speed issue ("it takes a lot time, to change a language"), loading an input language for the first time is not guaranteed to be a lightning fast operation. I see about a half a second delay on my machine using the normal mechanism. Not usually a big bottleneck; most people don't switch back and forth that many times. If you really need to speed this up, consider caching the return value of the LoadKeyboardLayout function.

Community
  • 1
  • 1
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • 1. Hi. I use this code in application that catches latinic key from keyboard and puts cyrilic key instead. For example, if I push 'k', I want 'к' to be printed, or if I press "sh", I want to see 'ш' in my screen)) To make this working, I must use hook. The problem is with keys, that are not characters. For example, is I press ';', I want ';' to be printed. The "simple" way is to do the same job, like I did with characters, I made a dictionary and, used a SendKey. But I think, that changing layout is better way. – Michael May 26 '13 at 11:55
  • 2. Whithout changing layout, all this stuff works well. Without hook, changing layout works good too. So the problem is with changing layout + hook. 3. I need to change laout to another apllication, not for application runnig this code, so InputLangue can't help me. – Michael May 26 '13 at 12:00
  • @Michael Unfortunately, you're never going to make this work the way you describe it with ligatures. A global keyboard hook doesn't retrieve multiple characters at a time, so it's impossible to convert `sh` into `ш`. You will never see `sh`, you'll see `s` and then—some time later—`h`. Beyond that, you don't need to change the input language to do what you described. With a hook, you can change the contents of the `KBDLLHOOKSTRUCT` structure directly. So you can just change whatever Latin character was initially entered to whatever Cyrillic character you desire. – Cody Gray - on strike May 26 '13 at 12:10
  • 1. "it's impossible to convert sh into ш" - I've already did it (s=с + h=х Backspase => ш). The problem is only with changing layout. 2. I can do it with one layout, but I want to use english too. So another possible solution is two english layouts, but I think that my solution is better (with two english layouts I'll need to write my own language bar in tray..) – Michael May 26 '13 at 12:22