キーボードのキーを押したことにする(SendInput)

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

以前の記事で、キーボードを押したことにするというのをやった。

tera1707.com

そのときは、「keybd_event」というAPIを使ってC++アプリで試作していたのだが、今回はC#アプリでpinvokeを使って「SendInput」というAPIを使ってやってみる。

やりたいこと その2

今回、別のウインドウ上で、特定のキーの組み合わせを押したことにしたかった。 (例えば、別のウインドウ上で「ALT + F4」を押して、そのアプリを閉じる、とか)

こちらのページを参考に、下記のようにやってみる。

  • まず、対象のウインドウを探す
  • 探したウインドウを「SetForegroundWindow()」関数で前面に出す
  • その状態で「SendInput()」関数でキーボードを押したことにする

実験コード

下記のようにして、SendInputでキー入力を送る。

PInvoke.SetForegroundWindow(wd.hWnd);

// ALT+F4を送る  ★★★★★ ココが肝!! ★★★★★
var inputs = new INPUT[4];

inputs[0].type = INPUT_TYPE.INPUT_KEYBOARD;
inputs[0].Anonymous.ki.wVk = VIRTUAL_KEY.VK_MENU;

inputs[1].type = INPUT_TYPE.INPUT_KEYBOARD;
inputs[1].Anonymous.ki.wVk = VIRTUAL_KEY.VK_F4;

inputs[2].type = INPUT_TYPE.INPUT_KEYBOARD;
inputs[2].Anonymous.ki.wVk = VIRTUAL_KEY.VK_F4;
inputs[2].Anonymous.ki.dwFlags = KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP;

inputs[3].type = INPUT_TYPE.INPUT_KEYBOARD;
inputs[3].Anonymous.ki.wVk = VIRTUAL_KEY.VK_MENU;
inputs[3].Anonymous.ki.dwFlags = KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP;

var inputsSpan = new Span<INPUT>(inputs);
var ret = PInvoke.SendInput(inputsSpan, Marshal.SizeOf<INPUT>());

※SendInput()の第二引数には、INPUT構造体の1つ分の大きさ(byte数)を入れる。(配列全体ではない!)  その値がおかしな値だと、SendInputは失敗して、戻り値が0になる。

 成功すると、成功したキー操作の数が帰ってくる。(上の例だと4)

実験コード全体

下記に置いている。

https://github.com/tera1707/WindowsMessageSenderWinui/blob/feature/SendShiftCtrlMtoOtherWindow/WindowMessageSender/WindowMessageSender/MainWindow.xaml.cs

using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Windows.Graphics;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Input.KeyboardAndMouse;

namespace WindowMessageSender
{
    public sealed partial class MainWindow : Window, INotifyPropertyChanged
    {

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName)
            => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        internal ObservableCollection<WindowData> WindowTitle
        {
            get { return _windowTitle; }
            set { _windowTitle = value; OnPropertyChanged(nameof(WindowTitle)); }
        }
        private ObservableCollection<WindowData> _windowTitle = new();

        public MainWindow()
        {
            this.InitializeComponent();

            // WinUI3のウインドウのハンドルを取得
            var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
            // そのウインドウのIDを取得
            Microsoft.UI.WindowId windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
            // そこからAppWindowを取得する
            Microsoft.UI.Windowing.AppWindow appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);

            appWindow.Resize(new SizeInt32(700, 900));
        }

        private void myButton_Click(object sender, RoutedEventArgs e)
        {
            WindowTitle.Clear();

            string windowName;
            string windoClass;

            PInvoke.EnumWindows(((hWnd, param) =>
            {
                int bufferSize = PInvoke.GetWindowTextLength(hWnd) + 1;
                unsafe
                {
                    var isVisible = PInvoke.IsWindowVisible(hWnd);

                    fixed (char* windowNameChars = new char[bufferSize])
                    {
                        if (PInvoke.GetWindowText(hWnd, windowNameChars, bufferSize) == 0)
                        {
                            int errorCode = Marshal.GetLastWin32Error();
                            if (errorCode != 0)
                                throw new Win32Exception(errorCode);

                            return true;
                        }

                        windowName = new string(windowNameChars);
                    }

                    fixed (char* windoClassChars = new char[256])
                    {
                        if (PInvoke.GetClassName(hWnd, windoClassChars, 256) == 0)
                        {
                            int errorCode = Marshal.GetLastWin32Error();
                            if (errorCode != 0)
                                throw new Win32Exception(errorCode);

                            return true;
                        }

                        windoClass = new string(windoClassChars);
                    }

                    if (VisibleOnly.IsOn)
                    {
                        WindowTitle.Add(new WindowData(hWnd, windowName, windoClass, isVisible));
                    }
                    else if (isVisible == true)
                    {
                        WindowTitle.Add(new WindowData(hWnd, windowName, windoClass, isVisible));
                    }

                    return true;
                }
            }), (LPARAM)0);

            WindowList.ItemsSource = WindowTitle;
        }

        private void MenuFlyoutItem_Click(object sender, RoutedEventArgs e)
        {
            Button_Click(null, null);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (WindowList.SelectedItem is WindowData wd)
            {
                var msg = UInt32.Parse(tbMsgNumber.Text, System.Globalization.NumberStyles.HexNumber);
                PInvoke.SetForegroundWindow(wd.hWnd);

                {
                    // ALT+F4を送る  ★★★★★ ココが肝!! ★★★★★
                    var inputs = new INPUT[4];

                    inputs[0].type = INPUT_TYPE.INPUT_KEYBOARD;
                    inputs[0].Anonymous.ki.wVk = VIRTUAL_KEY.VK_MENU;

                    inputs[1].type = INPUT_TYPE.INPUT_KEYBOARD;
                    inputs[1].Anonymous.ki.wVk = VIRTUAL_KEY.VK_F4;

                    inputs[2].type = INPUT_TYPE.INPUT_KEYBOARD;
                    inputs[2].Anonymous.ki.wVk = VIRTUAL_KEY.VK_F4;
                    inputs[2].Anonymous.ki.dwFlags = KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP;

                    inputs[3].type = INPUT_TYPE.INPUT_KEYBOARD;
                    inputs[3].Anonymous.ki.wVk = VIRTUAL_KEY.VK_MENU;
                    inputs[3].Anonymous.ki.dwFlags = KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP;

                    var inputsSpan = new Span<INPUT>(inputs);
                    var ret = PInvoke.SendInput(inputsSpan, Marshal.SizeOf<INPUT>());
                }
            }
        }
    }

    internal class WindowData
    {
        public HWND hWnd {  get; set; }
        public string Title { get; set; }
        public string ClassName { get; set; }
        public bool IsVisible { get; set; }

        public WindowData(HWND hwnd, string title, string className, bool isVisible)
        {
            hWnd = hwnd;
            Title = title;
            ClassName = className;
            IsVisible = isVisible;
        }
    }
}

参考

WinUI3でアプリでウインドウを探してそいつに好きなWM_Msgを送る実験

https://tera1707.com/entry/2024/07/16/214707

別ウインドウを操作する方法(別ウインドウにキーボード入力されたことにする方法)

https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11186154546

SendInput関数

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-sendinput

INPUT構造体

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/ns-winuser-input

配列からSpanの作り方

https://qiita.com/aka-nse/items/cea3c6f91413c3582b5f

仮想キーコード(VK_XXX)一覧

https://learn.microsoft.com/ja-jp/windows/win32/inputdev/virtual-key-codes