ゼロから作るRPA その1の4 マウス動作記録編(再生)


はじめに

その1の3では、マウスの動きを記録する機能を作りました。今回は、その記録した動作を再現する処理を作ります。

環境

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

  • Windows 10 pro
  • Visual Studio 2019 Community Edition

今回やること

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

ボタン追加

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

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

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の処理に追加します。

ファイル読み込み

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

DaphRPApp.cpp
    std::ifstream fin;
    fin.open(L"rec.log", std::ios::in);

ifstreamを使用してファイルを読み取ります。
次に、ファイルを一行ずつ読み込みます。

DaphRPApp.cpp
    while (true)
    {
        char line[128] = { 0 };
        fin >> line;
        if (line[0] == '\0')
            continue;
    }

>>を使って、一行ずつ読み込みます。空行が来たら処理を終了しています。
次に、ファイルの内容を解析します。
ファイルはCSV形式で、以下のフォーマットで保存されています。
メッセージ, X座標, Y座標, タイムスタンプ
そこで、strtok_sを使用して、カンマ区切り文字列をそれぞれ取得します。
取得した後は、それぞれの文字列を数値に変換し保持します。

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関数を使用します。

SendInput
UINT WINAPI SendInput(
  _In_ UINT    nInputs,
  _In_ LPINPUT pInputs,
  _In_ int     cbSize
);

SendInputは、キーボードやマウスをプログラムで操作するための関数です。
以下の通り、解析した値を設定して、SendInputを呼び出します。

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をスクリーンの幅と高さで割ったものをかけて算出しています。

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

動きを見てみましょう

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

つづく

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

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

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

その1の1
https://qiita.com/yasunari_matsuo/items/b1e56ad06c6a7843dfae
その1の2
https://qiita.com/yasunari_matsuo/items/a1d294f09ac21e9508b6
その1の3
https://qiita.com/yasunari_matsuo/items/14fe987a162936f7a1ae