1. Rowing0914

    Posted

    Rowing0914
Changes in title
+pythonでデスクトップツール開発のため、クリックを判定するライブラリを見つけた
Changes in tags
Changes in body
Source | HTML | Preview

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

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

やりたかったことは簡単です。
デスクトップ上で動くゲームや外部カメラを通じて流れてくる動画など、特定のWindowを経由して映像が流れることがあるとします。
それをずーっとトラッキングしたかったのです。
PILなどを使ってそれをベクトル変換できるのでそれを持って機械学習をしたかったのです。
ただ今回の肝はウィンドウサイズをユーザーが決められるようにしたいということです。
ですので、簡単に言えば、ネイティブスクリーンキャプチャツールwindowsで言えば、snipping toolのように。
macで言えば、shift + cmd + 4で出てくるアレみたいなやつです。
それで枠をくくった上で、その指定した範囲をpythonで見続けてもらうっていう感じです。
あれこれ書いても仕方がないので、結論から。

pynput

https://pypi.org/project/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