Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Properly detect keyboard layout

I have a winforms application where I need to obtain the current keyboard layout of the user. To do that I'm using System.Windows.Forms.InputLanguage.CurrentInputLanguage.LayoutName.

This works fine as long as the user has the form as his active window, once he focuses something else and changes the language the former property wont return a proper value, it will return the last used language while the form was still the active window.

Is there a way I can get the users keyboard layout's name even if he is not focusing the form, there are no restrictions to what can be used.

like image 371
Deadzone Avatar asked Jan 30 '23 02:01

Deadzone


2 Answers

As you might already know that the System.Windows.Forms.InputLanguage.CurrentInputLanguage.LayoutName property returns the keyboard layout for the current thread and no matter what layout you choose it would remain the same for the executing thread unless you select that window and change the keyboard input layout for that window.

That said, what you are essentially trying to do is check the current keyboard layout culture and be able to know when it changes. I had a similar requirement a while ago and I came up with the following code which served me well:

public delegate void KeyboardLayoutChanged(int oldCultureInfo, int newCultureInfo);

class KeyboardLayoutWatcher : IDisposable
{
    private readonly Timer _timer;
    private int _currentLayout = 1033;


    public KeyboardLayoutChanged KeyboardLayoutChanged;

    public KeyboardLayoutWatcher()
    {
        _timer = new Timer(new TimerCallback(CheckKeyboardLayout), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
    }

    [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow();
    [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hwnd, IntPtr proccess);
    [DllImport("user32.dll")] static extern IntPtr GetKeyboardLayout(uint thread);
    public int GetCurrentKeyboardLayout()
    {
        try
        {
            IntPtr foregroundWindow = GetForegroundWindow();
            uint foregroundProcess = GetWindowThreadProcessId(foregroundWindow, IntPtr.Zero);
            int keyboardLayout = GetKeyboardLayout(foregroundProcess).ToInt32() & 0xFFFF;

            if (keyboardLayout == 0)
            {
                // something has gone wrong - just assume English
                keyboardLayout = 1033;
            }
            return keyboardLayout;
        }
        catch (Exception ex)
        {
            // if something goes wrong - just assume English
            return 1033;
        }
    }

    private void CheckKeyboardLayout(object sender)
    {
        var layout = GetCurrentKeyboardLayout();
        if (_currentLayout != layout && KeyboardLayoutChanged != null)
        {
            KeyboardLayoutChanged(_currentLayout, layout);
            _currentLayout = layout;
        }

    }

    private void ReleaseUnmanagedResources()
    {
        _timer.Dispose();
    }

    public void Dispose()
    {
        ReleaseUnmanagedResources();
        GC.SuppressFinalize(this);
    }

    ~KeyboardLayoutWatcher()
    {
        ReleaseUnmanagedResources();
    }
}

And use it like:

        new KeyboardLayoutWatcher().KeyboardLayoutChanged += (o, n) =>
        {
            this.CurrentLayoutLabel.Text = $"{o} -> {n}"; // old and new KB layout
        };
like image 146
Digvijay Avatar answered Feb 02 '23 10:02

Digvijay


Couple of things to note here. Keyboard selection in Windows is done on a per-thread basis. This allows the user to select a different keyboard locale for any given application, which Windows will respect, while leaving other applications alone.

The user does this by enabling the language bar (for installed keyboards) in the Windows Taskbar. If they select a different keyboard while another application has the focus, then the selection only applies to that application. And besides, if your application doesn't have the focus, it can't do anything about it anyway. By using the language bar in this way, the user is plainly stating their intention to have the selected keyboard apply to the active application only. There is no way for your application to find out about it, since from Windows' point of view, it's none of your application's business.

Now, if you want to know if the user changes the keyboard for the entire system (using the control panel applet), that's feasible. If your application doesn't have the focus, there's no way to trap the notification message. Your form will still consider the current language to be the one it started with. However, the system-wide change does change the InputLanguage.DefaultInputLanguage. to the newly-selected keyboard. So, override the OnActivated handler and check the value of the default language, not the current one (which is per-thread).

like image 25
Mark Benningfield Avatar answered Feb 02 '23 09:02

Mark Benningfield