Determine if Windows 10 Touch Keyboard is Visible or Hidden

筅森魡賤 提交于 2019-11-29 11:54:10

I've done some research with Spy++ . Looks like the new keyboard in Fall Creators Update (ver. 1709) is hosted by another window. This window has Windows.UI.Core.CoreWindow class and Microsoft Text Input Application as its title.

The following code works for all Windows 10 versions including the new 1803 and older Windows versions as well (starting with Windows 8, I believe).

static class TouchKeyboard
{
    public static bool GetIsOpen()
    {
        return GetIsOpen1709() ?? GetIsOpenLegacy();
    }

    private static bool? GetIsOpen1709()
    {
        var parent = IntPtr.Zero;
        for (;;)
        {
            parent = FindWindowEx(IntPtr.Zero, parent, WindowParentClass1709);
            if (parent == IntPtr.Zero)
                return null; // no more windows, keyboard state is unknown

            // if it's a child of a WindowParentClass1709 window - the keyboard is open
            var wnd = FindWindowEx(parent, IntPtr.Zero, WindowClass1709, WindowCaption1709);
            if (wnd != IntPtr.Zero)
                return true;
        }
    }

    private static bool GetIsOpenLegacy()
    {
        var wnd = FindWindowEx(IntPtr.Zero, IntPtr.Zero, WindowClass);
        if (wnd == IntPtr.Zero)
            return false;

        var style = GetWindowStyle(wnd);
        return style.HasFlag(WindowStyle.Visible)
            && !style.HasFlag(WindowStyle.Disabled);
    }

    private const string WindowClass = "IPTip_Main_Window";
    private const string WindowParentClass1709 = "ApplicationFrameWindow";
    private const string WindowClass1709 = "Windows.UI.Core.CoreWindow";
    private const string WindowCaption1709 = "Microsoft Text Input Application";

    private enum WindowStyle : uint
    {
        Disabled = 0x08000000,
        Visible = 0x10000000,
    }

    private static WindowStyle GetWindowStyle(IntPtr wnd)
    {
        return (WindowStyle)GetWindowLong(wnd, -16);
    }

    [DllImport("user32.dll", SetLastError = false)]
    private static extern IntPtr FindWindowEx(IntPtr parent, IntPtr after, string className, string title = null);

    [DllImport("user32.dll", SetLastError = false)]
    private static extern uint GetWindowLong(IntPtr wnd, int index);
}

Update: I updated the answer and the code to be compatible with Redstone 4 (v1803) as well.

I discovered yet another undocumented COM API that returns the position of the touch keyboard. It returns the bounds of the keyboard window or zeroes if the keyboard is hidden. I tested it in Windows 8.1, Windows 10 and Windows 10 Fall Creators Update and it works fine.

Now some bad news: in all versions prior to Fall Creators Update it only reports accurate results if the active window and the touch keyboard are located on the same monitor. If this is not the case - the API just returns the previous cached value. I'm guessing it has something to do with the fact that this API was meant to be used to calculate occlusion of the touch keyboard and your app's window. (It's called inside Windows::UI::ViewManagement::InputPane.get_OccludedRect() UWP API).

So if you don't care about supporting older versions or multi-monitor scenarios - use it. Otherwise I would suggest checking the Windows version and falling back to the previous method (GetIsOpenLegacy() from my other answer).

The API:

[ComImport, Guid("228826af-02e1-4226-a9e0-99a855e455a6")]
class ImmersiveShellBroker
{
}

[ComImport, Guid("9767060c-9476-42e2-8f7b-2f10fd13765c")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IImmersiveShellBroker
{
    void Dummy();
    IInputHostManagerBroker GetInputHostManagerBroker();
}

[ComImport, Guid("2166ee67-71df-4476-8394-0ced2ed05274")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IInputHostManagerBroker
{
    void GetIhmLocation(out Rect rect, out DisplayMode mode);
}

[StructLayout(LayoutKind.Sequential)]
struct Rect
{
    public int Left, Top, Right, Bottom;
}

enum DisplayMode
{
    NotSupported = 0,
    Floating = 2,
    Docked = 3,
}

Usage example:

// do this once:
var brokerClass = new ImmersiveShellBroker();
var broker = (IImmersiveShellBroker)brokerClass;
var ihm = broker.GetInputHostManagerBroker();
Marshal.ReleaseComObject(broker);

// now ihm reference can be cached and used later:
Rect rect;
DisplayMode mode;
ihm.GetIhmLocation(out rect, out mode);

Note: looks like GetIhmLocation() always returns DisplayMode.NotSupported instead of the actual mode prior to Windows 10.

I'm using this solution, and it is working on Windows 1607, 1709 and 1803 (check the Main method below on the code):

using System;
using System.Drawing;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {

        [ComImport, Guid("D5120AA3-46BA-44C5-822D-CA8092C1FC72")]
        public class FrameworkInputPane
        {
        }

        [ComImport, System.Security.SuppressUnmanagedCodeSecurity,
        InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
        Guid("5752238B-24F0-495A-82F1-2FD593056796")]
        public interface IFrameworkInputPane
        {
            [PreserveSig]
            int Advise(
                [MarshalAs(UnmanagedType.IUnknown)] object pWindow,
                [MarshalAs(UnmanagedType.IUnknown)] object pHandler,
                out int pdwCookie
                );

            [PreserveSig]
            int AdviseWithHWND(
                IntPtr hwnd,
                [MarshalAs(UnmanagedType.IUnknown)] object pHandler,
                out int pdwCookie
                );

            [PreserveSig]
            int Unadvise(
                int pdwCookie
                );

            [PreserveSig]
            int Location(
                out Rectangle prcInputPaneScreenLocation
                );
        }


        static void Main(string[] args)
        {
            var inputPane = (IFrameworkInputPane)new FrameworkInputPane();
            inputPane.Location(out var rect);
            Console.WriteLine((rect.Width == 0 && rect.Height == 0) ? "Keyboard not visible" : "Keyboard visible");
        }
    }
}

It uses the IFrameworkInputPane interface (https://docs.microsoft.com/en-us/windows/desktop/api/shobjidl_core/nn-shobjidl_core-iframeworkinputpane)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!