Открыть сенсорную клавиатуру через команду

Вопрос или проблема

Открыть экранную клавиатуру можно с помощью команды, используя c:\program files\common files\microsoft shared\ink\tabtip.exe (источник).

Это хорошо работает в Windows 10, но я не смог заставить это работать в Windows 11 (экранная клавиатура не появляется). Однако я не получаю никаких сообщений об ошибках (tabtip.exe работает как процесс), поэтому предполагаю, что происходит что-то другое. Может кто-то подтвердить это поведение?

PS конечная цель – создать сочетание клавиш для экранной клавиатуры.

Дополнение01: Я нашел проблему: задача tabtip.exe не закрывается, когда экранная клавиатура закрывается (например, через X), повторный запуск tabtip.exe ничего не делает, поскольку задача уже запущена. Завершение задачи ранее может быть решением, но это требует повышенной оболочки, поэтому сочетание клавиш несколько теряет смысл. Любые идеи о том, как это решить, приняты с благодарностью!

Внимание: метод, описанный в этом ответе, основан на undocumented реализациях Windows Shell, например, undocumented методах COM и undocumented названиях процессов. Он может сломаться в любой момент с любым обновлением. Microsoft не рекомендует использовать undocumented поведение, но, насколько мне известно, в настоящее время нет поддерживаемого способа программно открыть экранную клавиатуру.

В двух ответах на Stack Overflow @torvin и @Andrea S. описывают как программно открыть экранную клавиатуру и как определить, открыта ли панель ввода (экранная клавиатура или панель рукописного ввода) в данный момент.

Мы можем использовать их методы, чтобы создать PowerShell скрипт, который переключает экранную клавиатуру:

# Скрипт для переключения экранной клавиатуры Windows 11,
# совместимый как с Windows PowerShell, так и с PowerShell 7.
# На основе кода от @torvin (https://stackoverflow.com/users/332528/torvin): https://stackoverflow.com/a/40921638
# На основе кода от @Andrea S. (https://stackoverflow.com/users/5887913/andrea-s): https://stackoverflow.com/a/55513524

# Внимание: полагается на undocumented поведение Windows Shell
# и может сломаться с любым обновлением.
# В последний раз тестировался на 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);
    }
}
'@

# Переключите экранную клавиатуру независимо от того, показана она в данный момент или нет.
[TouchKeyboardController]::ToggleTouchKeyboard()

# Или, если вы хотите только показать экранную клавиатуру
# и не скрывать ее, если она уже активна:
# if (-not [TouchKeyboardController]::IsInputPaneOpen()) {
#     [TouchKeyboardController]::ToggleTouchKeyboard()
# }

Скрипт работает следующим образом: если TabTip.exe не работает, мы запускаем его через ShellExecute, что заставляет появиться экранной клавиатуре. Если TabTip.exe уже работает, мы вызываем ITipInvocation.Toggle(), чтобы переключить состояние отображения экранной клавиатуры. (TabTip.exe, похоже, является своего рода COM-сервером для UIHostNoLaunch.)

Если вы хотите, чтобы скрипт просто открывал экранную клавиатуру и не переключал ее (т.е. не скрывал, когда она уже показана), вам сначала нужно проверить, активна ли экранная клавиатура. К счастью, есть поддерживаемый API для этого: IFrameworkInputPane.Location(). Мы можем использовать это, чтобы прервать операцию, если уже есть активная панель ввода (см. комментарии в скрипте).

Чтобы создать сочетание клавиш для переключения экранной клавиатуры, вы можете сохранить скрипт выше, например, как ToggleTouchKeyboard.ps1, затем создать ярлык на рабочем столе для скрипта и, наконец, назначить сочетание клавиш для ярлыка рабочего стола (щелкнув правой кнопкой мыши на ярлыке рабочего стола > Свойства).


Если вы не можете использовать 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>
/// Определяет, открыта ли в данный момент панель ввода (экранная клавиатура или панель рукописного ввода).
/// </summary>
/// <returns>
/// Если функция выполнена успешно и панель ввода в данный момент открыта, возвращается S_OK.
/// Если функция выполнена успешно и панель ввода в данный момент не открыта, возвращается S_FALSE.
/// 
/// В противном случае возвращается другой код ошибки.
/// </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"Не удалось инициализировать COM." << endl;
        return 1;
    }

    // Переключите экранную клавиатуру независимо от того, показана она в данный момент или нет.
    // Чтобы только показать экранную клавиатуру и не скрывать ее, если она уже активна,
    // прервитесь здесь, если 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"Запущен TabTip.exe для открытия экранной клавиатуры." << endl;
        }
        else
        {
            wcerr << L"Не удалось запустить TabTip.exe. Ошибка: " << result << endl;
        }
    }
    else if (SUCCEEDED(hr))
    {
        HWND desktopWindow = GetDesktopWindow();
        hr = tip->Toggle(desktopWindow);
        if (SUCCEEDED(hr))
        {
            wcout << L"Переключена экранная клавиатура через ITipInvocation.Toggle()." << endl;
        }
        else
        {
            wcerr << L"Не удалось переключить экранную клавиатуру через ITipInvocation.Toggle()." << endl;
        }
        tip->Release();
    }

    CoUninitialize();
}

Ответ или решение

Чтобы открыть экранную клавиатуру Touch Keyboard в Windows 11 с помощью команд, можно использовать PowerShell-скрипт, который позволяет переключать состояние клавиатуры. Этот подход был основан на исследовании методов, описанных пользователями на Stack Overflow.

Пошаговая инструкция по созданию скрипта для открытия Touch Keyboard

  1. Создание PowerShell-скрипта:

    Откройте текстовый редактор и вставьте следующий код:

    # Скрипт для переключения экранной клавиатуры Touch Keyboard в Windows 11
    
    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);
        }
    }
    '@
    
  2. Сохранение скрипта:

    Сохраните данный код в файл с расширением .ps1 (например, ToggleTouchKeyboard.ps1).

  3. Создание ярлыка для запуска скрипта:

    • Щелкните правой кнопкой мыши на рабочем столе и выберите "Создать" > "Ярлык".
    • Введите следующую команду:
      powershell.exe -ExecutionPolicy Bypass -File "C:\путь\к\файлу\ToggleTouchKeyboard.ps1"
    • Замените "C:\путь\к\файлу\ToggleTouchKeyboard.ps1" на фактический путь к вашему скрипту.
    • Нажмите "Далее" и дайте ярлыку имя (например, "Переключить экранную клавиатуру").
  4. Назначение горячей клавиши:

    • Щелкните правой кнопкой мыши на созданном ярлыке и выберите "Свойства".
    • В поле "Горячая клавиша" введите комбинацию клавиш, например, Ctrl + Alt + T.
    • Нажмите "ОК" для сохранения изменений.

Примечания

  • Следует помнить, что этот метод опирается на неофициальные детали реализации Windows, которые могут измениться с обновлениями системы. Microsoft не поддерживает использование таких методов.
  • Если ваш компьютер управляется администратором, и вам недоступна PowerShell, вы можете воспользоваться скомпилированной программой на C++ для выполнения аналогичных действий.

С помощью вышеуказанных инструкций вы сможете открыть или переключить экранную клавиатуру в Windows 11 с помощью команды. Этот скрипт должен работать в большинстве ситуаций, но тестируйте его с учетом собственных требований и условий системы.

Оцените материал
Добавить комментарий

Капча загружается...