通过命令打开触摸键盘

通过命令打开触摸键盘

可以使用c:\program files\common files\microsoft shared\ink\tabtip.exe(命令打开触摸键盘来源)。

它在 Windows 10 中运行良好,但我无法在 Windows 11 下使用它(屏幕键盘不显示)。但我没有收到任何错误消息(tabtip.exe 作为进程运行),所以我假设发生了其他事情。有人可以验证这种行为吗?

PS 最终目标是为触摸键盘创建键盘快捷键

添加01:我发现了这个问题:当触摸键盘关闭时(例如通过 X),tabtip.exe 任务不会关闭,再次启动 tabtip.exe 不会有任何反应,因为任务已经在运行。提前结束任务可能是一个解决方案,但它需要提升的 shell,因此键盘快捷键有点过时了。任何关于如何解决这个问题的想法都值得赞赏!

答案1

警告:此答案中描述的技术依赖于 Windows Shell 的未记录的实现细节,例如未记录的 COM 方法和未记录的进程名称。它可能会随时因任何更新而中断。Microsoft 不鼓励使用未记录的行为,但据我所知,目前不支持以编程方式打开触摸键盘。

在 Stack Overflow 上的两个答案中,@托文@Andrea S.描述如何以编程方式打开触摸键盘如何确定输入窗格(触摸键盘或手写面板)当前是否打开

我们可以使用他们的方法来创建一个切换触摸键盘的 PowerShell 脚本:

# 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.
[TouchKeyboardController]::ToggleTouchKeyboard()

# 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()
# }

该脚本的工作方式如下:如果 TabTip.exe 未运行,我们通过 启动它ShellExecute,这样触摸键盘就会出现。如果 TabTip.exe 已在运行,我们调用ITipInvocation.Toggle()来切换触摸键盘的显示状态。(TabTip.exe 似乎是 UIHostNoLaunch 的某种 COM 服务器。)

如果您希望脚本只打开触摸键盘而不切换它(即,当它已经显示时不隐藏它),您首先必须检查触摸键盘是否已处于活动状态。幸运的是,有一个支持此功能的 API:。IFrameworkInputPane.Location()如果已经有一个活动的输入窗格,我们可以使用它来中止(请参阅脚本中的注释)。

要创建用于切换触摸键盘的键盘快捷键,您可以将上述脚本保存为ToggleTouchKeyboard.ps1,然后为该脚本创建桌面快捷方式,最后为桌面快捷方式分配键盘快捷键(通过右键单击桌面快捷方式>属性)。


如果您无法在系统上使用 PowerShell/.NET(因为它已被管理员出于安全原因阻止),那么您必须在安装了 Visual Studio 的计算机上编译一个直接调用 Windows API 的小型 C++ 程序:

#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**)&frameworkInputPane)};
    if (SUCCEEDED(hr))
    {
        hr = frameworkInputPane->Location(&rect);
        if (SUCCEEDED(hr))
        {
            hr = IsRectEmpty(&rect) ? S_FALSE : S_OK;
        }
        frameworkInputPane->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**)&tip);

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

    CoUninitialize();
}

相关内容