[C #] Start Windows standard touch keyboard

3 minute read

Windows has a touch keyboard executable file called tabtip.exe (“C: /Program Files / Common Files / Microsoft Shared / ink / tabtip.exe”), and you can use the touch keyboard by executing this. I will.

There is also a screen keyboard called osk.exe, but I won’t cover it this time.
(The UI is not for touch & ScrLk and some buttons that are troublesome when pressed, so I do not want to use it too much)

code

(Corrected on 2020/09/02)
Moved TabTip.exe startup process from Open method to Toggle method.
Fixed to execute Open method via Task.

using System;
using System.Diagnostics;
using System.Threading;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

/// <summary>
///Windows standard touch keyboard operation class.
/// </summary>
public class WinTouchKeyboard
{
    [ComImport, Guid("D5120AA3-46BA-44C5-822D-CA8092C1FC72")]
    private class FrameworkInputPane
    {
    }

    [ComImport, System.Security.SuppressUnmanagedCodeSecurity,
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid("5752238B-24F0-495A-82F1-2FD593056796")]
    private 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
            );
    }

    [ComImport, Guid("4ce576fa-83dc-4F88-951c-9d0782b4e376")]
    private class UIHostNoLaunch
    {
    }

    [ComImport, Guid("37c994e7-432b-4834-a2f7-dce1f13b834b")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface ITipInvocation
    {
        void Toggle(IntPtr hwnd);
    }

    [DllImport("user32.dll", SetLastError = false)]
    private static extern IntPtr GetDesktopWindow();

    /// <summary>
    /// TabTip.exe path
    /// </summary>
    public static string TabTipPath { get; set; } = "C:/Program Files/Common Files/Microsoft Shared/ink/tabtip.exe";

    /// <summary>
    ///Open the touch keyboard.
    ///If the touch keyboard is displayed, do nothing.
    /// </summary>
    /// <param name="delayMsec">Waiting time until the touch keyboard is displayed.
    ///If the touch keyboard closes immediately in response to the TAB key, adjust the wait time. (About 200-300 msec)</param>
    public static void Open(int delayMsec)
    {
        if (IsVisible())
        {
            return;
        }

        Task.Run(() =>
        {
            if (delayMsec > 0)
            {
                Thread.Sleep(delayMsec);
            }

            //Touch keyboard ON/OFF switching
            Toggle();
        });
    }

    /// <summary>
    ///Close the touch keyboard.
    ///If the touch keyboard is not displayed, do nothing.
    /// </summary>
    public static void Close()
    {
        if (!IsVisible())
        {
            return;
        }

        //Touch keyboard ON/OFF switching
        Toggle();
    }

    /// <summary>
    ///Touch keyboard ON/OFF switching
    /// </summary>
    public static void Toggle()
    {
        //UIHostNoLaunch.Switch display with Toggle
        try
        {
            UIHostNoLaunch uiHostNoLaunch = null;
            try
            {
                uiHostNoLaunch = new UIHostNoLaunch();
                var tipInvocation = (ITipInvocation)uiHostNoLaunch;
                tipInvocation.Toggle(GetDesktopWindow());
            }
            finally
            {
                if (uiHostNoLaunch != null)
                {
                    Marshal.ReleaseComObject(uiHostNoLaunch);
                }
            }
        }
        catch (COMException)
        {
            //tabtip.new UIHostNoLaunch if exe is not running()COMException occurs in.
            //Process.I also thought that I should check in advance with GetProcessesByName,
            //Even if COMException is ignored, there is no significant speed difference, so do not check.
        }

        //If an exception occurs in the above process, tabtip.The exe is not started, so start it
        Process.Start(TabTipPath);
    }

    /// <summary>
    ///Get the display status of the touch keyboard.
    /// </summary>
    /// <returns>true:Display false:Hide</returns>
    public static bool IsVisible()
    {
        //Get the position and size of the touch keyboard, and if the width is 0, it is considered hidden.
        Rectangle bounds = GetBounds();
        return (bounds.Width != 0);
    }

    /// <summary>
    ///Get the position and size of the touch keyboard.
    /// </summary>
    /// <returns>Touch keyboard position / size</returns>
    public static Rectangle GetBounds()
    {
        IFrameworkInputPane inputPane = null;
        Rectangle rect;
        try
        {
            //Get the position and size of the touch keyboard, and if the width is 0, it is considered hidden.
            inputPane = (IFrameworkInputPane)new FrameworkInputPane();
            inputPane.Location(out rect);
        }
        finally
        {
            if (inputPane != null)
            {
                Marshal.ReleaseComObject(inputPane);
            }
        }

        return rect;
    }
}

I made it into a class by referring to the post on Stackoverflow.

Launching the touch keyboard (Tabtip.exe) from non-admin account on Windows 10
Show touch keyboard (TabTip.exe) in Windows 10 Anniversary edition
Determine if Windows 10 Touch Keyboard is Visible or Hidden

We have confirmed that it works with Win10Pro 1903. It is unknown if it will work with other versions.
(Please let me know if it works on other than Win10)
ITipInvocation and UIHostNoLaunch are COMs that are not documented by MS officials, so I can’t say if they can be used, but they work for the time being.

When closing the touch keyboard, there were cases where the tabtip.exe process was forcibly killed or SendMessage sent WM_SYSCOMMAND + SC_CLOSE to close the window.

–tabtip.exe quits (tabtip.exe must be restarted each time)
–The touch keyboard may remain even if the “IPTIP_Main_Window” window disappears.

It seems that it is better not to close with these methods because of the problem.