ゼロから作るRPA　その１の４　マウス動作記録編（再生）

#はじめに
[その１の３](https://qiita.com/yasunari_matsuo/items/14fe987a162936f7a1ae)では、マウスの動きを記録する機能を作りました。今回は、その記録した動作を再現する処理を作ります。

#環境
私の使用している環境を以下に挙げます。環境が異なる場合は適宜読み替えてください。

- Windows 10 pro
- Visual Studio 2019 Community Edition

#今回やること

1. 画面に「再生」ボタンを追加する
2. ファイルを読み込む
3. 記録したマウス操作ごとに、マウスを動作させる

#ボタン追加
記録ボタンを追加した時と同様に、再生ボタンをダイアログに追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508288/982a501f-f4a3-c628-9620-a4034ce23734.png)

次にダイアログのコールバック関数に、再生ボタンクリックのメッセージ処理を追加します。

```C++:DaphRPApp.cpp
	case WM_COMMAND:
		switch (LOWORD(wp)) 
		{
		case IDC_REC_BTN:
			StartMouseHook(hDlgWnd);
			return FALSE;
		case IDC_PLAY_BTN:
			return FALSE;
		default:
			return FALSE;
		}
```

ボタンのIDを`IDC_PLAY_BTN`としたので、そのボタンの処理を`WM_COMMAND`の処理に追加します。

#ファイル読み込み
記録の時に保存したマウスイベントログファイルを読み込みます。

```C++:DaphRPApp.cpp
	std::ifstream fin;
	fin.open(L"rec.log", std::ios::in);
```
`ifstream`を使用してファイルを読み取ります。
次に、ファイルを一行ずつ読み込みます。

```C++:DaphRPApp.cpp
	while (true)
	{
		char line[128] = { 0 };
		fin >> line;
		if (line[0] == '\0')
			continue;
	}
```
`>>`を使って、一行ずつ読み込みます。空行が来たら処理を終了しています。
次に、ファイルの内容を解析します。
ファイルはCSV形式で、以下のフォーマットで保存されています。
`メッセージ, X座標, Y座標, タイムスタンプ`
そこで、`strtok_s`を使用して、カンマ区切り文字列をそれぞれ取得します。
取得した後は、それぞれの文字列を数値に変換し保持します。

```C++:DaphRPApp.cpp
		char* ctx = nullptr;

		char* token = strtok_s(line, ",", &ctx);
		if (token == nullptr)
			continue;

		int msg = atoi(token);

		token = strtok_s(nullptr, ",", &ctx);
		if (token == nullptr)
			continue;

		int x = atoi(token);

		token = strtok_s(nullptr, ",", &ctx);
		if (token == nullptr)
			continue;

		int y = atoi(token);

		token = strtok_s(nullptr, ",", &ctx);
		if (token == nullptr)
			continue;

		int timestamp = atoi(token);
```
これで、保存したマウスメッセージの解析は完了です。

#マウス動作
解析した値を使用して、マウスを動かします。
マウスを動かすために、`SendInput`関数を使用します。

```C++:SendInput
UINT WINAPI SendInput(
  _In_ UINT    nInputs,
  _In_ LPINPUT pInputs,
  _In_ int     cbSize
);
```
`SendInput`は、キーボードやマウスをプログラムで操作するための関数です。
以下の通り、解析した値を設定して、`SendInput`を呼び出します。

```C++:SendInput
		int time = prevtime == 0 ? 0 : timestamp - prevtime;
		if (time < 0)
			time = 0;

		INPUT inp[1] = { 0 };
		inp[0].type = INPUT_MOUSE;
		inp[0].mi.time = 0;
		inp[0].mi.dwExtraInfo = 0;
		inp[0].mi.dx = x * (65535 / GetSystemMetrics(SM_CXSCREEN));
		inp[0].mi.dy = y * (65535 / GetSystemMetrics(SM_CYSCREEN));
		inp[0].mi.mouseData = 0;
		inp[0].mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;

		switch (msg)
		{
		case WM_LBUTTONDOWN:
			inp[0].mi.dwFlags |= MOUSEEVENTF_LEFTDOWN;
			break;
		case WM_LBUTTONUP:
			inp[0].mi.dwFlags |= MOUSEEVENTF_LEFTUP;
			break;
		case WM_MOUSEMOVE:
			break;
		case WM_MOUSEWHEEL:
			break;
		case WM_MOUSEHWHEEL:
			break;
		case WM_RBUTTONDOWN:
			inp[0].mi.dwFlags |= MOUSEEVENTF_RIGHTUP;
			break;
		case WM_RBUTTONUP:
			inp[0].mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN;
			break;
		}

		Sleep(time);
		SendInput(1, inp, sizeof(INPUT));
```
`INPUT`構造体に、`INPUT_MOUSE`を指定して、マウスを動作させることを指定します。
そのあとは、xやyを設定します。これらの値は、画面の幅や高さを65535までで表す必要があるため、65535をスクリーンの幅と高さで割ったものをかけて算出しています。

ホイールの動作は今のところ未対応なので、処理を入れていません。暇を見つけて拡張します。

#動きを見てみましょう
動画だと、ただ絵を描いているだけに見えるかもしれませんが、記録したものを再生した動画です。本当ですよ～。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">マウス動作を記録再生できるRPAをつくって、お絵かきを記録して再生してみた。 <a href="https://t.co/0gEdxJ3Y6K">https://t.co/0gEdxJ3Y6K</a> via <a href="https://twitter.com/YouTube?ref_src=twsrc%5Etfw">@YouTube</a></p>&mdash; teamm (@teamm89201677) <a href="https://twitter.com/teamm89201677/status/1184799300636659714?ref_src=twsrc%5Etfw">October 17, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#つづく
これで、マウス動作の記録と再生ができるようになりました。UWSCでいうところの、低レベル記録です。
キーボードの記録再生は、同じようにフックして記録して再生すればいいので、簡単に作れると思います。
もし需要があれば書きますので、コメントください。

次回は、OpenCVを使った、画像指定による項目クリックの実装です。
https://qiita.com/yasunari_matsuo/items/1dd10e0379570eef96d0

これまでの記事は以下です。

その１の１
https://qiita.com/yasunari_matsuo/items/b1e56ad06c6a7843dfae
その１の２
https://qiita.com/yasunari_matsuo/items/a1d294f09ac21e9508b6
その１の３
https://qiita.com/yasunari_matsuo/items/14fe987a162936f7a1ae