Search code examples
c#winapimodal-dialogpostmessage

How to send WM_INPUTLANGCHANGEREQUEST to app with modal window?


I wrote a keyboard switcher, which works well, but fails if current application has modal window opened. On keyboard switch I do the following

hwnd = GetForegroundWindow();
PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, IntPtr.Zero, handle);

where

[DllImport("User32.dll", EntryPoint = "PostMessage")]
private static extern int PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();

but the language does not change.

How would I accomplish this?


Adding get root owner improved situation, but didn't help completely.

Adding call for GetDesktopWindow didn't help:

hwnd = GetDesktopWindow();
InputLangChangeRequest(hwnd, language);
hwnd = GetRootOwner();
InputLangChangeRequest(hwnd, language);

Code is here https://github.com/dims12/NormalKeyboardSwitcher


Solution

  • Use GetAncestor

    Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.

    This should return the main UI window if there is a modal window, or a chain of modal window.

    hwnd = GetForegroundWindow();
    hwnd = GetAncestor(hwnd, GA_ROOTOWNER); //#define GA_ROOTOWNER 3
    


    Apparently WM_INPUTLANGCHANGEREQUEST fails if the target itself is a dialog based application (I don't know why!) To solve the problem you can post WM_INPUTLANGCHANGEREQUEST message to dialog's descendants (in addition to WM_INPUTLANGCHANGEREQUEST message to the dialog itself)

    static bool MyEnumProc(IntPtr hwnd, IntPtr lParam)
    {
        PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, IntPtr.Zero, lParam);
        return true;
    }
    
    static void Foo()
    {
        //Greek input for testing:
        var hkl = LoadKeyboardLayout("00000408", KLF_ACTIVATE);
        var hwnd = GetForegroundWindow();
        if (hwnd != null)
        {
            hwnd = GetAncestor(hwnd, GA_ROOTOWNER);
            PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, IntPtr.Zero, (IntPtr)hkl);
    
            StringBuilder buf = new StringBuilder(100);
            GetClassName(hwnd, buf, 100);
    
            //if this is a dialog class then post message to all descendants 
            if (buf.ToString() == "#32770")
                EnumChildWindows(hwnd, MyEnumProc, (IntPtr)hkl);
        }
    }