Seaside Laboratory

Posts

怪盗ロワイヤル用ウィンクツール作成記 (マウス編)

ウィンクツール作成にあたり色々と調べたことを備忘録として書いておく。久しぶりの Windows プログラミング (実は DirectX だと Win32 に殆ど触れない) だったので、なかなか苦労した。

マウスカーソルの移動は SetCursorPos で簡単に移動できるのだが、何故か単純なクリック関数は用意されていない。調べてみたところ、SendInput という関数を使えばクリックができるらしいので MSDN で検索をしてみた。解説によると、この関数はキーボードからマウスまで扱える汎用的な作りで、どういう入力をするかは引数の INPUT 構造体で指定する仕組みらしい。

UINT SendInput
(
    UINT nInputs,    // 入力イベントの数
    LPINPUT pInputs, // 挿入する入力イベントの配列
    int cbSize       // 構造体のサイズ
);

引数にある INPUT 構造体については日本語マニュアルに解説がないので英語版を見ることになる。

typedef struct tagINPUT
{
    DWORD type;
    union
    {
        MOUSEINPUT mi;
        KEYBDINPUT ki;
        HARDWAREINPUT hi;
    };
} INPUT, *PINPUT;

そしてここで使われている MOUSEINPUT 構造体も日本語版の解説がない。

typedef struct tagMOUSEINPUT
{
    LONG dx;               // 水平位置または移動量
    LONG dy;               // 垂直位置または移動量
    DWORD mouseData;       // ホイールの移動
    DWORD dwFlags;         // 移動とクリックのオプション
    DWORD time;
    ULONG_PTR dwExtraInfo; // アプリケーション定義の情報
} MOUSEINPUT, *PMOUSEINPUT;

ただ、mouse_event という関数の引数がこの構造体と一部同じっぽいので、日本語版の mouse_event を参照するとある程度意味が掴める。

マウス操作の度に SendInput を呼び出すのは辛いので、ネット上のサンプルプログラムを参考にしながらラッパー関数を作成した。

// マウスのクリック
void ClickMouse()
{
    INPUT inputs[] =
    {
        { INPUT_MOUSE, { 0, 0, 0, MOUSEEVENTF_LEFTDOWN, 0, 0 } },
        { INPUT_MOUSE, { 0, 0, 0, MOUSEEVENTF_LEFTUP  , 0, 0 } }
    };

    ::SendInput( 2, inputs, sizeof( INPUT ) );
}

クリックは「押す」だけと思いがちだが「押す」と「離す」がセットになって初めて成立する。

次はマウスの移動。

// マウスの移動
void MoveMouse( int nX, int nY )
{
    int dx = nX * 65535 / GetSystemMetrics( SM_CXSCREEN );
    int dy = nY * 65535 / GetSystemMetrics( SM_CYSCREEN );

    INPUT inputs[] =
    {
        { INPUT_MOUSE, { dx, dy, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, 0, 0 } }
    };
    ::SendInput( 1, inputs, sizeof( INPUT ) );
}

SetCursorPos を使わずに自作関数を用意したのは、SendInput と混在させたときにうまく動かなかったため。おそらく、入力ストリームの関係で移動とクリックの前後関係が保証されなくなるのが原因と思われる。なので、移動も SendInput 版を作っておくと安心。

あと特殊なのは、関数の頭の方で dx と dy を計算しているところ。

MOUSEEVENTF_ABSOLUTE フラグを指定した場合、dx と dy の各パラメータは 0 ~ 65,535 の範囲で、正規化された絶対座標を保持しています。イベントプロシージャは、これらの座標を表示サーフェスの座標へマップ(変換)します。座標 (0,0) は表示サーフェスの左上隅、(65535,65535) は表示サーフェスの右下隅へマップされます。

という少し変わった仕様なので、現在の座標を画面の大きさで割って割合を算出し、それを 65535 でスケーリングしてやると関数が求めている値になる。