pythonでデスクトップツール開発のため、クリックを判定するライブラリを見つけた

すいません、自分用の記事として描かせていただきます。

めちゃめちゃ探すのに苦労しました。。。
1日ぐらいかかったかも。。

やりたかったことは簡単です。
デスクトップ上で動くゲームや外部カメラを通じて流れてくる動画など、特定のWindowを経由して映像が流れることがあるとします。
それをずーっとトラッキングしたかったのです。
PILなどを使ってそれをベクトル変換できるのでそれを持って機械学習をしたかったのです。
ただ今回の肝はウィンドウサイズをユーザーが決められるようにしたいということです。
ですので、簡単に言えば、ネイティブスクリーンキャプチャツールの動画版ということです。

windowsで言えば、snipping toolのように。
macで言えば、`shift + cmd + 4`で出てくるアレみたいなやつです。
それで枠をくくった上で、その指定した範囲をpythonで見続けてもらうっていう感じです。
あれこれ書いても仕方がないので、結論から。

## pynput
https://pypi.org/project/pynput/

```python
from pynput import mouse

def on_move(x, y):
    print('Pointer moved to {0}'.format((int(x), int(y))))

def on_click(x, y, button, pressed):
    print('{0} at {1}'.format('Pressed' if pressed else 'Released',(int(x), int(y))))
    if not pressed:
        # Stop listener
        return False

def on_scroll(x, y, dx, dy):
    print('Scrolled {0} at {1}'.format(
        'down' if dy < 0 else 'up',
        (x, y)))

# Collect events until released
with mouse.Listener(
        on_move=on_move,
        on_click=on_click,
        on_scroll=on_scroll) as listener:
    listener.join()
```
これでマウスのトラッキングとクリックした時のJSできなイベントを起動させることができました。


では、ここから何が僕に起こったのかをちょっと書きます。
## めっちゃ時間を食った時に考えていた案

1. _get_windows()：こっちを使うと、Quartzのライブラリを使ってMacOSの程レイヤーな部分から現状動いているアプリケーション等の情報を持って来れます。
そして、そこからトラックしたアプリケーションのウィンドウのサイズとポジションを取得して、マニュアルでそいつを捕捉してやろうとしてました。できまs。。。。したが、なぜかPILにその`(x,y,w,h)`情報を渡す部分でうまくいかず、常に的外れな場所のキャプチャを続けるという始末。
2. get_windows()：死闘のあげく発見した、AppleScriptというWindowsのWSH的なやつ。。それで、下の方にはったget_info_game.scptを書いて、それをsubprocessで読んできて、1版と同様にマニュアルで敵を捕捉。しかし、やっぱりPILがうまくいかない。

```python
"""
get_windows() is obtained from the link below
https://superuser.com/questions/902869/how-to-identify-which-process-is-running-which-window-in-mac-os-x
"""
import Quartz
import gym
import numpy as np
from PIL import ImageGrab, Image
import subprocess

def show_windows():
	wl = Quartz.CGWindowListCopyWindowInfo( Quartz.kCGWindowListOptionAll, Quartz.kCGNullWindowID)
	wl = sorted(wl, key=lambda k: k.valueForKey_('kCGWindowOwnerPID'))

	print 'PID'.rjust(7) + ' ' + 'WinID'.rjust(5) + '  ' + 'x,y,w,h'.ljust(21) + ' ' + '\t[Title] SubTitle'
	print '-'.rjust(7,'-') + ' ' + '-'.rjust(5,'-') + '  ' + '-'.ljust(21,'-') + ' ' + '\t-------------------------------------------'

	for v in wl:
	    print ( \
	        str(v.valueForKey_('kCGWindowOwnerPID') or '?').rjust(7) + \
	        ' ' + str(v.valueForKey_('kCGWindowNumber') or '?').rjust(5) + \
	        ' {' + ('' if v.valueForKey_('kCGWindowBounds') is None else \
	            ( \
	                str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('X')))     + ',' + \
	                str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('Y')))     + ',' + \
	                str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('Width'))) + ',' + \
	                str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('Height'))) \
	            ) \
	            ).ljust(21) + \
	        '}' + \
	        '\t[' + ((v.valueForKey_('kCGWindowOwnerName') or '') + ']') + \
	        ('' if v.valueForKey_('kCGWindowName') is None else (' ' + v.valueForKey_('kCGWindowName') or '')) \
	    ).encode('utf8')

	return "done"

def _get_windows():
	wl = Quartz.CGWindowListCopyWindowInfo( Quartz.kCGWindowListOptionAll, Quartz.kCGNullWindowID)
	wl = sorted(wl, key=lambda k: k.valueForKey_('kCGWindowOwnerPID'))
	
	dimensions = {}
	cnt = 0

	# create dict to store the position for the window
	for v in wl:
		if (v.valueForKey_('kCGWindowOwnerName') == 'Sublime Text'):
			x = int(v.valueForKey_('kCGWindowBounds').valueForKey_('X'))
			y = int(v.valueForKey_('kCGWindowBounds').valueForKey_('Y'))
			w = int(v.valueForKey_('kCGWindowBounds').valueForKey_('Width'))
			h = int(v.valueForKey_('kCGWindowBounds').valueForKey_('Height'))
			dimensions["{}".format(cnt)] = (x,y,w,h)
			cnt += 1

	print(dimensions)
	return dimensions

def get_windows():
	result = subprocess.check_output(["osascript", "get_info_game.scpt"]).strip().split(',')
	result = map(int, result)
	x,y,w,h = result
	result = (x+50,y+50,w+x+600,h+y+450)
	return result

def _screenshot(dimensions):
	dimension = dimensions["0"]
	print(dimension)
	
	# save image
	ImageGrab.grab(dimension).save("png_{}.png".format(0))

	image = ImageGrab.grab(dimension)
	image.load()
	data = np.asarray(image, dtype="int32")
	print(data)

def screenshot(dimension):
	# save image
	ImageGrab.grab(dimension).save("png_{}.png".format(0))

	image = ImageGrab.grab(dimension)
	image.load()
	data = np.asarray(image, dtype="int32")
	print(data)

if __name__ == '__main__':
	# show_windows()
	dimension = get_windows()
	print(dimension)
	screenshot(dimension)
```

```applescript:get_info_game.scpt
tell application "System Events" to tell application process "AppName入れてね"
    tell window 1
    	activate
        get {position, size}
    end tell
end tell
```

## 未知との遭遇
[Pramod Kumar Misra, knows Marathi मराठी](https://www.quora.com/How-do-I-detect-mouse-events-left-click-with-Python)
この人のstackoverflowの回答を見て、愕然。
もうほとんどのpython GUIツール用のライブラリがWindows仕様であることに泣いていたのですが、上の記事を数時間の死闘ののちに発見をして、やっと回答にたどり着きました。
こんな経験が、もし何かをお役に立てれば幸いです。