Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

めちゃめちゃ探すのに苦労しました。。。
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

未知との遭遇

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away