PythonでWindows APIを使ったマウス操作

# きっかけ
去年の終わり頃にTwitterで某FPSゲームのアンチリコイルマクロを紹介した動画を見まして、~~マクロの地道な調整は大変だろうな~~不正ツールに近いと思ったのですが同時に、予め設定された動きをゲーム内で実行できれば何か面白いことができるのではないかと閃いたわけです。
# 試行錯誤
Pythonではマウスとキーボード操作をするPyAutoGUIやpynputなどのライブラリがあり、これらを利用することで簡単に操作、自動化ができます。しかし、私が遊んでいるゲームは操作を受け付けませんでした。DirectXが絡んでる？
色々検索すると、[Serpent.AI](http://serpent.ai/)というAIにゲームをさせる代物を発見しました。Serpent.AIはWin32APIでゲームの操作をしているようで、私が遊んでいるゲームはWin32APIで操作できました。

# 環境
- Windows 10 64bit
- Python 3.7.4

# コードを書く
Win32APIでマウスを操作するには2つの方法があります。
1.SetCursorPosを使う
2.SendInputを使う
## 1.SetCursorPosを使う
PythonからWin32APIを呼び出すために標準ライブラリのctypesを使います。

```python:input_1.py
import ctypes

#SetCursorPos(x, y)
ctypes.windll.user32.SetCursorPos(200, 300)
```
x,y (横,縦)で指定するだけです。(0,0)が画面左上で右下に向かって値が大きくなります。
このコードではx:200 y:300にカーソルを移動させます。
しかし、SetCursorPosはゲームの操作ができません。
## 2.SendInputを使う
マウスカーソルの移動、クリック、キー入力などができます。
詳しい使い方は[こちら](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-sendinput)を見てください。
コードは[これ](https://github.com/SerpentAI/SerpentAI/blob/b8eec1032c5b1134125fd1124387a3cd9f253d5f/serpent/input_controllers/native_win32_input_controller.py)と[これ](https://stackoverflow.com/questions/49973060/simplifying-ctype-union-in-python-to-send-keyboard-events-in-windows)を参考にさせていただきました。

SendInputに必要なINPUT構造体と、マウス操作の情報を入れるMOUSEINPUT構造体を宣言します。

```python:input_2.py
import ctypes

ULONG_PTR = ctypes.POINTER(ctypes.c_ulong)

# マウスイベントの情報
class MOUSEINPUT(ctypes.Structure):
    _fields_ = [("dx", ctypes.c_long),
                ("dy", ctypes.c_long),
                ("mouseData", ctypes.c_ulong),
                ("dwFlags", ctypes.c_ulong),
                ("time", ctypes.c_ulong),
                ("dwExtraInfo", ULONG_PTR)]


class INPUT(ctypes.Structure):
    _fields_ = [("type", ctypes.c_ulong),
                ("mi", MOUSEINPUT)]
```
SendInputの引数と戻り値の型を指定します。

```python:input_2.py
LPINPUT = ctypes.POINTER(INPUT)

SendInput = ctypes.windll.user32.SendInput
SendInput.argtypes = (ctypes.c_uint, LPINPUT, ctypes.c_int)
SendInput.restype = ctypes.c_uint
```
座標を変換してSendInputに突っ込めば、マウスカーソルが動きます。

```python:input_2.py
x, y = 200, 300
x = x * 65536 // 1920
y = y * 65536 // 1080
_mi = MOUSEINPUT(x, y, 0, (0x0001 | 0x8000), 0, None)
SendInput(1, INPUT(0, _mi), ctypes.sizeof(INPUT))
```
詳しい説明は長くなるのですが、
この2行ではカーソルの座標(200,300)を0~65,535の座標(6826,18204)に変換しています。
画面解像度によって除数は変わります。
割り算は切り捨てにしていますが四捨五入と大差ないので気にしません。
後述しますが、絶対座標での移動にのみ必要です。

```python:input_2.py
x = x * 65536 // 1920 # x * 65536 // 画面解像度(横)
y = y * 65536 // 1080 # y * 65536 // 画面解像度(縦)
```
ホイールの回転量は120で標準的なマウスの1ノッチです。120の倍数以外の整数でも問題ありません。横スクロールもできます。
マウスイベントはビット演算子の`|`演算子で複数設定できます。
よく使うものを表にまとめました。
詳細は[こちら](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/ns-winuser-mouseinput)

| 値      |       意味 |
|:-----------------|------------------:|
| 0x8000           |絶対座標を使う、指定しない場合は相対座標 |
| 0x0001            |       カーソルを移動させるか |
|0x0002|左ボタンを押す|
|0x0004|左ボタンを離す|
|0x0008|右ボタンを押す|
|0x0010|右ボタンを離す|
| 0x01000           |            横スクロールするか |
| 0x0800　　　　　   |　　　　縦スクロールするか|
ここで一つ注意が必要です。絶対座標を使うか、相対座標を使うかで座標の形式が変わります。
絶対座標を使う場合は0~65,535の座標に変換する必要がありますが、相対座標を使う場合は座標の変換は必要ありません。今回の場合は(200,300)のままです。

```python:input_2.py
# MOUSEINPUT(x座標, y座標, ホイールの回転量, マウスイベント, 0, None)
_mi = MOUSEINPUT(x, y, 0, (0x0001 | 0x8000), 0, None)
```
入力の種類は3つあり、下表のようになっています。

| 値      |       意味 |
|:-----------------|------------------:|
|0| マウス入力|
|1| キーボード入力|
|2| ハードウェア入力|

```python:input_2.py
# SendInput(1, INPUT(入力の種類, _mi), ctypes.sizeof(INPUT))
SendInput(1, INPUT(0, _mi), ctypes.sizeof(INPUT))
```
#まとめ
SendInputではマウス操作の他にキーボード操作もできますが、簡略化のためマウス操作に絞りました。
次回があれば、キーボード操作を含めた説明にするつもりです。

# 最後に
この記事を読まれている方には関係ないかと思いますが、プログラム自体はかなり前に完成しています。
プログラムを書いている時は動けばヨシと深く考えずにやっていたので、後から見返すとトンデモナイことになっていました。コメントは入れてました。
なので、整理ついでにプログラムの要を記事にしました。
こうして記事にすると忘れないようになりますね。