4
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-07-08

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

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

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

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

pynput

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がうまくいかない。
"""
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)
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 मराठी
この人のstackoverflowの回答を見て、愕然。
もうほとんどのpython GUIツール用のライブラリがWindows仕様であることに泣いていたのですが、上の記事を数時間の死闘ののちに発見をして、やっと回答にたどり着きました。
こんな経験が、もし何かをお役に立てれば幸いです。

4
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?