Открыть сенсорную клавиатуру с помощью команды
Открытие сенсорной клавиатуры должно быть возможным по команде с помощьюc:\program files\common files\microsoft shared\ink\tabtip.exe
(источник).
Он отлично работает в Windows 10, но мне не удалось заставить его работать в Windows 11 (экранная клавиатура не отображается). Однако я не получаю никаких сообщений об ошибках (tabtip.exe запускается как процесс), поэтому я предполагаю, что происходит что-то еще. Может ли кто-нибудь проверить это поведение?
PS конечная цель - создать сочетание клавиш для сенсорной клавиатуры
Дополнение01: Я обнаружил проблему: задача tabtip.exe не закрывается при закрытии сенсорной клавиатуры (например, через X), повторный запуск tabtip.exe ничего не дает, поскольку задача уже запущена. Решением может быть предварительное завершение задачи, но для этого требуется оболочка с повышенными правами, поэтому сочетание клавиш несколько устаревает. Любые идеи о том, как решить эту проблему, приветствуются!
1 ответ
Предупреждение. Метод, описанный в этом ответе, основан на недокументированных деталях реализации оболочки Windows, например недокументированных методах COM и недокументированном имени процесса. Он может сломаться в любой момент с любым обновлением. Microsoft не рекомендует использовать недокументированное поведение, но, насколько мне известно, в настоящее время не существует поддерживаемого способа программного открытия сенсорной клавиатуры.
В двух ответах на Stack Overflow Tim McLean и @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 не запущен, запускаем его через
Если вы хотите, чтобы сценарий просто открывал сенсорную клавиатуру, а не переключал ее (т. е. не скрывал ее, когда она уже отображается), сначала необходимо проверить, активна ли сенсорная клавиатура. К счастью, для этого есть поддерживаемый API:
Чтобы создать сочетание клавиш для переключения сенсорной клавиатуры, вы можете сохранить приведенный выше скрипт, например, как
Если вы не можете использовать PowerShell/.NET в своей системе (поскольку он был заблокирован вашим администратором по соображениям безопасности), вам необходимо скомпилировать небольшую программу C++ на компьютере с установленной Visual Studio, которая напрямую вызывает API-интерфейсы Windows:
#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();
}