Search code examples
c#windowsuwpkeyboard-layout

Keyboard layout change not detected in universal windows apps with C#


I wanna detect every keyboard layout change with C#. It doesn't matter whether it is switched by win + space or alt + shift or with mouse... I wrote a code that works considerably well (see below) for desktop apps. But it doesn't work for UWP apps. If I switch layout while within the UWP app, it is not detected, if I switch to desktop app the change is detected right away... How can I do it to detect any change in the layout? Is there any other way how to find out what layout is active at any given moment no matter what window is active? My code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Globalization;

namespace ConsoleApplication1
{
class Program
{
    static void Main(string[] args)
    {
        string lastLang = "";
        while (true)
        {
            string langSuffix = GetKeyboardLayoutIdAtTime();

            if (!langSuffix.Equals(lastLang))
            {
                // do something
                Console.WriteLine(getCurrentTimeStamp() + ": Changing '" + lastLang + "' to '" + langSuffix + "'.");
                lastLang = langSuffix;
            }
            System.Threading.Thread.Sleep(1000);

        }

    }


    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern int GetWindowThreadProcessId(IntPtr handleWindow, out int lpdwProcessID);

    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern IntPtr GetKeyboardLayout(int WindowsThreadProcessID);

    public static string GetKeyboardLayoutIdAtTime()
    {
        IntPtr hWnd = GetForegroundWindow();
        int lpdwProcessId;
        InputLanguageCollection installedInputLanguages = InputLanguage.InstalledInputLanguages;
        CultureInfo currentInputLanguage = null;
        int WinThreadProcId = GetWindowThreadProcessId(hWnd, out lpdwProcessId);

        IntPtr KeybLayout = GetKeyboardLayout(WinThreadProcId);
        // this remain unchanged when I switch layouts in UWP
        Console.WriteLine("KL IntPtr: " + KeybLayout);

        for (int i = 0; i < installedInputLanguages.Count; i++)
        {
            if (KeybLayout == installedInputLanguages[i].Handle) currentInputLanguage = installedInputLanguages[i].Culture;

        }
        if(currentInputLanguage == null)
        {
            Console.WriteLine(getCurrentTimeStamp() + "current input language is null...");
        }
        return currentInputLanguage.TwoLetterISOLanguageName;
    }

    private static string getCurrentTimeStamp()
    {
        return DateTime.Now.ToString("yyyyMMddHHmmssffff");
    }
}

}


Solution

  • In Desktop, we use the Input Method Manager to communicate with an input method editor (IME), which runs as a service. In UWP, we should be able to use the Text Services Framework. There is a document about Alternatives to Windows APIs in Universal Windows Platform (UWP) apps.

    So we should be able to use the Windows​.UI​.Text​.Core namespace, it provides types for accessing the Windows core text APIs and the text input server. Windows core text is a client-server system that centralizes the processing of keyboard input into a single server.

    We can find the InputLanguageChanged event in the CoreTextServicesManager class. It occurs when the current input language has changed. When we switched the input method by win + space or alt + shift or with mouse, the InputLanguageChanged will be fired.

    For example:

    public MainPage()
    {
        this.InitializeComponent();
        CoreTextServicesManager textServiceManager = CoreTextServicesManager.GetForCurrentView();
        textServiceManager.InputLanguageChanged += TextServiceManager_InputLanguageChanged;
    }
    
    private void TextServiceManager_InputLanguageChanged(CoreTextServicesManager sender, object args)
    {
        Debug.WriteLine("Keyboard layout is changed!");
    }