2

Opening the touch keyboard should be possible by command using c:\program files\common files\microsoft shared\ink\tabtip.exe (source).

It works fine in Windows 10 but I couldn't get it to work under Windows 11 (the on-screen keyboard doesn't show up). I don't get any error messages though (tabtip.exe runs as a process), so I assume something else is going on. Can anyone verify this behavior?

PS the final goal is to create a keyboard shortcut for the touch keyboard

Addition01: I found the problem: the tabtip.exe task doesn't close when the touch keyboard is closed (eg via the X), starting tabtip.exe again does nothing since the task is already running. Ending the task prior could be a solution but it requires an elevated shell so the keyboard shortcut is somewhat rendered obsolete. Any ideas on how to solve this are appreciated!

Albin
  • 11,950

2 Answers2

2

Warning: The technique described in this answer relies on undocumented implementation details of the Windows Shell, e.g., undocumented COM methods and an undocumented process name. It may break at any time with any update. Microsoft discourages the use of undocumented behaviour, but as far as I know, there is currently no supported way to open the touch keyboard programmatically.

In two answers on Stack Overflow, @torvin and @Andrea S. describe how to open the Touch Keyboard programmatically and how to determine whether an input pane (Touch Keyboard or Handwriting Panel) is currently open.

We can use their methods to create a PowerShell script which toggles the Touch Keyboard:

# A script to toggle the Touch Keyboard of Windows 11,
# compatible with both Windows PowerShell and PowerShell 7.
# Based on code by @torvin (https://stackoverflow.com/users/332528/torvin): https://stackoverflow.com/a/40921638
# Based on code by @Andrea S. (https://stackoverflow.com/users/5887913/andrea-s): https://stackoverflow.com/a/55513524

Warning: Relies on undocumented behaviour of the Windows Shell

and may break with any update.

Last tested on Windows 11 Home 22000.978.

Add-Type -ReferencedAssemblies $(if ($PSVersionTable.PSEdition -eq "Desktop") {"System.Drawing.dll"} else {$null}) -Language CSharp -TypeDefinition @' using System; using System.Diagnostics; using System.Drawing; using System.Runtime.InteropServices;

public class TouchKeyboardController { public static void ToggleTouchKeyboard() { try { UIHostNoLaunch uiHostNoLaunch = new UIHostNoLaunch(); ((ITipInvocation)uiHostNoLaunch).Toggle(GetDesktopWindow()); Marshal.ReleaseComObject(uiHostNoLaunch); } catch (COMException exc) { if (exc.HResult == unchecked((int)0x80040154)) // REGDB_E_CLASSNOTREG { ProcessStartInfo processStartInfo = new ProcessStartInfo("TabTip.exe") { UseShellExecute = true }; using (Process process = Process.Start(processStartInfo)) { } } else { throw; } } }

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

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

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


public static bool IsInputPaneOpen()
{
    FrameworkInputPane frameworkInputPane = new FrameworkInputPane();
    Rectangle rect;
    ((IFrameworkInputPane)frameworkInputPane).Location(out rect);
    Marshal.ReleaseComObject(frameworkInputPane);
    return !rect.IsEmpty;
}

[ComImport, Guid("d5120aa3-46ba-44c5-822d-ca8092c1fc72")]
public class FrameworkInputPane
{
}

[ComImport, Guid("5752238b-24f0-495a-82f1-2fd593056796")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFrameworkInputPane
{
    int Advise([MarshalAs(UnmanagedType.IUnknown)] object pWindow, [MarshalAs(UnmanagedType.IUnknown)] object pHandler, out int pdwCookie);
    int AdviseWithHWND(IntPtr hwnd, [MarshalAs(UnmanagedType.IUnknown)] object pHandler, out int pdwCookie);
    int Unadvise(int pdwCookie);
    int Location(out Rectangle prcInputPaneScreenLocation);
}

} '@

Toggle the Touch Keyboard regardless of whether it is currently shown or not.

Alternatively, if you only want to show the Touch Keyboard

and not hide it if it is already active:

if (-not TouchKeyboardController::IsInputPaneOpen()) {

TouchKeyboardController::ToggleTouchKeyboard()

}

The script works as follows: If TabTip.exe is not running, we start it via ShellExecute, which causes the Touch Keyboard to appear. If TabTip.exe is already running, we call ITipInvocation.Toggle() to toggle the display state of the Touch Keyboard. (TabTip.exe appears to be some kind of COM server for UIHostNoLaunch.)

If you want the script to just open the Touch Keyboard and not toggle it (i.e. not hide it when it is already shown), you first have to check if the Touch Keyboard is already active. Luckily, there is a supported API for this: IFrameworkInputPane.Location(). We can use this to abort if there is already an active input pane (see the comments in the script).

To create a keyboard shortcut for toggling the Touch Keyboard, you can save the script above e.g. as ToggleTouchKeyboard.ps1, then create a desktop shortcut for the script and finally assign a keyboard shortcut to the desktop shortcut (via right-click on the Desktop shortcut > Properties).


If you cannot use PowerShell/.NET on your system (because it has been blocked by your administrator for security reasons), you have to compile a small C++ program on a computer with Visual Studio installed that calls the Windows APIs directly:

#include <iostream>
#include <initguid.h>
#include <Objbase.h>
#include <Shobjidl.h>

// 4ce576fa-83dc-4F88-951c-9d0782b4e376 DEFINE_GUID(CLSID_UIHostNoLaunch, 0x4CE576FA, 0x83DC, 0x4f88, 0x95, 0x1C, 0x9D, 0x07, 0x82, 0xB4, 0xE3, 0x76);

// 37c994e7_432b_4834_a2f7_dce1f13b834b DEFINE_GUID(IID_ITipInvocation, 0x37c994e7, 0x432b, 0x4834, 0xa2, 0xf7, 0xdc, 0xe1, 0xf1, 0x3b, 0x83, 0x4b);

struct ITipInvocation : IUnknown { virtual HRESULT STDMETHODCALLTYPE Toggle(HWND wnd) = 0; };

using namespace std;

/// <summary> /// Determines whether an input pane (Touch Keyboard or handwriting Panel) is currently open. /// </summary> /// <returns> /// If the function succeeded and an input pane is currently open, S_OK is retured. /// If the function succeeded and no input pane is currently open, S_FALSE is returnd. /// /// Otherwise, another error code is returned. /// </returns> HRESULT IsInputPaneOpen() { RECT rect; ZeroMemory(&rect, sizeof(rect));

IFrameworkInputPane* frameworkInputPane{ nullptr };
HRESULT hr{ CoCreateInstance(CLSID_FrameworkInputPane, NULL, CLSCTX_INPROC_SERVER, IID_IFrameworkInputPane, (void**)&amp;frameworkInputPane)};
if (SUCCEEDED(hr))
{
    hr = frameworkInputPane-&gt;Location(&amp;rect);
    if (SUCCEEDED(hr))
    {
        hr = IsRectEmpty(&amp;rect) ? S_FALSE : S_OK;
    }
    frameworkInputPane-&gt;Release();
}

return hr;

}

int main() { HRESULT hr { CoInitialize(NULL) }; if (FAILED(hr)) { wcerr << L"Failed to initialize COM." << endl; return 1; }

// Toggle the Touch Keyboard regardless of whether it is currently shown or not.
// To only show the Touch Keyboard and not hide it if it is already active,
// abort here if IsInputPaneOpen() == S_OK.

ITipInvocation* tip{ nullptr };
hr = CoCreateInstance(CLSID_UIHostNoLaunch, NULL, CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER, IID_ITipInvocation, (void**)&amp;tip);

if (hr == REGDB_E_CLASSNOTREG)
{
    INT_PTR result = (INT_PTR)ShellExecuteW(NULL, NULL, L&quot;TabTip.exe&quot;, NULL, NULL, SW_SHOWNORMAL);
    if (result &gt; 32)
    {
        wcout &lt;&lt; L&quot;Started TabTip.exe to open Touch Keyboard.&quot; &lt;&lt; endl;
    }
    else
    {
        wcerr &lt;&lt; L&quot;Failed to start TabTip.exe. Error: &quot; &lt;&lt; result &lt;&lt; endl;
    }
}
else if (SUCCEEDED(hr))
{
    HWND desktopWindow = GetDesktopWindow();
    hr = tip-&gt;Toggle(desktopWindow);
    if (SUCCEEDED(hr))
    {
        wcout &lt;&lt; L&quot;Toggled the touch keyboard via ITipInvocation.Toggle().&quot; &lt;&lt; endl;
    }
    else
    {
        wcerr &lt;&lt; L&quot;Failed to toggle the Touch Keyboard via ITipInvocation.Toggle().&quot; &lt;&lt; endl;
    }
    tip-&gt;Release();
}

CoUninitialize();

}

1

As info, on Windows OS build 26100.2033 (Win11 Home 24H2 [ARM64]), toggling the Touch Keyboard via this PowerShell script didn't present to work. Running it from within the ISE revealed that the Execution Policy was set such that even running it from the context menu (expected Bypass EP to be applicable) wasn't working. I ended up overtly forcing the EP to Bypass for the CurrentUser context. Thereafter it is toggling as expected with the script.