[Python] マウスカーソルの形状判定

# はじめに

`pyautogui`でマウスカーソル形状で判定するために`win32gui.GetCursorInfo()`で状態を取得していましたが、返り値である<strong>ハンドル</strong>がどの形状を表しているのかいまいちわかっていなかったので調べてみました。

# マウスカーソル形状の種類

そもそもWindows標準のマウスカーソル形状は以下のようなものがあるようです。
（画像引用元：https://atmarkit.itmedia.co.jp/fdotnet/dotnettips/617changecursor/changecursor.html</gray>）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079481/f203d8d3-5bed-4a77-f10f-8ebec2cae361.png)


# win32gui.GetCursorInfo()で得られる返り値

参考としたサイトから、`win32con.IDC_*`で設定される定数から、`win32gui.LoadCursor(0, win32con.IDC_*)`でハンドルが得られることがわかりました。

|返り値|win32con<br>定数名|win32con<br>定数値|意味|
|-|-|-|-|
|65561|IDC_APPSTARTING|32650|標準の矢印カーソルと小さい砂時計カーソル|
|65539|IDC_ARROW|32512|標準の矢印カーソル|
|65545|IDC_CROSS|32515|十字カーソル|
|65567|IDC_HAND|32649|Windows 2000：ハンドカーソル|
|65563|IDC_HELP|32651|矢印と疑問符|
|65541|IDC_IBEAM|32513|アイビーム（ 縦線）カーソル|
|0|IDC_ICON|32641|使われていない*。代わりにIDC_ARROWを使う。|
|65559|IDC_NO|32648|禁止カーソル（ 円に左上から右下への斜線）|
|0|IDC_SIZE|32640|使われていない*。代わりに IDC_SIZEALL を使う。|
|65557|IDC_SIZEALL|32646|4 方向の矢印カーソル|
|65551|IDC_SIZENESW|32643|右上と左下を指す両方向矢印カーソル|
|65555|IDC_SIZENS|32645|上下を指す両方向矢印カーソル|
|65549|IDC_SIZENWSE|32642|左上と右下を指す両方向矢印カーソル|
|65553|IDC_SIZEWE|32644|左右を指す両方向矢印カーソル|
|65547|IDC_UPARROW|32516|上を指す垂直の矢印カーソル|
|65543|IDC_WAIT|32514|砂時計カーソル|

\* バージョンが 4.0 以降（Windows NT 4.0 以降）とマークされたアプリケーションにおいて

一覧は以下のコードから得られます（「意味」は参考サイトを引用）。

```Python:
import win32con
from win32gui import LoadCursor

DEFAULT_CURSORS = {
    win32con.IDC_APPSTARTING: 'appStarting',
    win32con.IDC_ARROW: 'Arrow',
    win32con.IDC_CROSS: 'Cross',
    win32con.IDC_HAND: 'Hand',
    win32con.IDC_HELP: 'Help',
    win32con.IDC_IBEAM: 'IBeam',
    win32con.IDC_ICON: 'ICon',
    win32con.IDC_NO: 'No',
    win32con.IDC_SIZE: 'Size',
    win32con.IDC_SIZEALL: 'sizeAll',
    win32con.IDC_SIZENESW: 'sizeNESW',
    win32con.IDC_SIZENS: 'sizeNS',
    win32con.IDC_SIZENWSE: 'sizeNWSE',
    win32con.IDC_SIZEWE: 'sizeWE',
    win32con.IDC_UPARROW: 'upArrow',
    win32con.IDC_WAIT: 'Wait',
}

for key, value in DEFAULT_CURSORS.items():
    print(f'{LoadCursor(0, key)}, {key}, {value}')
```
```:出力結果
65561, 32650, appStarting
65539, 32512, Arrow
65545, 32515, Cross
65567, 32649, Hand
65563, 32651, Help
65541, 32513, IBeam
0, 32641, ICon
65559, 32648, No
0, 32640, Size
65557, 32646, sizeAll
65551, 32643, sizeNESW
65555, 32645, sizeNS
65549, 32642, sizeNWSE
65553, 32644, sizeWE
65547, 32516, upArrow
65543, 32514, Wait
```

# pyautoguiでの使用例

というわけで、pyautoguiで判定する際には以下のような関数が、意図するところがわかりやすい気がしています。

```Python:
import pyautogui
from win32con import IDC_APPSTARTING, IDC_WAIT
from win32gui import LoadCursor, GetCursorInfo

def wait_hourglass(**kwargs):
    wait_limit = kwargs.pop('wait_limit', 60) * 5
    hourglass = [LoadCursor(0, IDC_APPSTARTING), LoadCursor(0, IDC_WAIT)]
    for _ in range(wait_limit):
        pyautogui.failSafeCheck()
        if GetCursorInfo()[1] not in hourglass:
            return GetCursorInfo()[1]
        sleep(0.2)
    raise TimeoutError()
```

# 参考サイト

https://stackoverflow.com/questions/59608898/how-to-get-the-state-of-the-cursor

https://www.tokovalue.jp/function/LoadCursor.htm

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadcursora

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.cursors?view=windowsdesktop-7.0&redirectedfrom=MSDN#properties