Chienomi

コマンド×X Window System×キーボード/マウス×ウィンドウ

Live With Linux::technique

この記事はコマンドでウィンドウの情報を得たり操作したり、 コマンドからキーボードやマウスの操作をしたり、 逆にキーボードやマウスからコマンドを発生させたりするものである。

今のGoogleだと検索したときに探しているものとは逆のものに吸われて、どうキーワードを変えてもまともに検索できないので、ここに書くことにした。

コマンドでキーボードやマウスを操作する

「オートメーション」とか「RPA」とか場合によっては「マクロ」とか呼ばれているものに有効。

xdotool (えっくすどぅーつぅーる) というものがあり、こちらで操作することができる。

キーボード

キーボード操作を発生させるには

xdotool key a

のようにする。 Shiftを伴う場合は

xdotool key shift+a

Ctrlを伴う場合は

xdotool key ctrl+a

CtrlとAltとShiftの同時押しなら

xdotool ctrl+alt+shift+a

のようにする。

非アルファベットキーは

Return, Space, Tab, BackSpace, Up, Down, Left, Right, Home F1, Page_Up, Printなど。 Superキー(Windowsキー)はSuper_L, Super_Rがある。

テキスト入力はtypeというアクションがあり、

xdotool type Hello

とかできる。

マウス

マウス関連は指定座標に移動する

xdotool mousemove 200 200

や、相対的に移動させる

xdotool mousemove_relative -- -50 50

負の値を与えるときには--が必要なことに注意。

クリックするときはclick。マウスボタンの番号を与えるのだが、Windows流に「左クリック、右クリック」とか覚えている人は要注意。 一般的には左クリックが1, 右クリックが2, ホイールクリックが3になっている。でも、3ボタンマウスを使っていると左から順に1, 2, 3だ(そんな人はほぼいないだろうけど)。

xdotool click 1

ダブルクリックはこう

xdotool click --repeat 2 1

分ける必要があるならmousedownmouseupを使う。

ゲームとかで自動化しようと思うならば、windowsizewindomoveが有効。 WindowIDを予め取得する必要があるが

xdotool windowmove 11111111 0 0
xdotool windowsize 11111111 1000 600

とかすればウィンドウ座標が一定になるから、座標指定が楽になる。

同じような機能を持つものとしてxautomation(コマンドはxte)もある。 ただし、機能はxdotoolのほうが多いようだ。

デスクトップから情報を取得する

WindowIDを得る

ウィンドウ操作に必要なウィンドウIDを得る方法で、一番実用的なのはクリックしたウィンドウのWindowsIDを得るselectwindow

xdotool selectwindow

このIDはxwininfo-idオプションでも使える。

ウィンドウの座標と大きさ

xwininfoを使う…のだけども注意が必要な部分もある。

-frameをつけるとウィンドウマネージャのフレームを含むようになるのだが、これをすると正確な値が出ない。 -frameをつけるのは諦めたほうが良い(slopを使えば取れる)。

また、-geometryの値はおそらくほしい値ではない。 これは、Corenersの左上からRelativeのXとYを引いた値と、Cornersの右下の差分で出しているのだが、単純にAbsoluteの値とWidth/Heightで得る値が正しい。 つまり、

eval $(xwininfo -id 11111111 | sed 's/^ *//' | grep -e '^Absolute' -e '^Height' -e '^Width' | sed -e 's/: */=/' -e 's/^/_wininfo_/' | tr ' -' '__')

って感じ。

xdotoolにもgetwindowgeometryというコマンドがあるのだが、こちらも正確な値が得られない。

後述するようにslopを使う方法もある。

slop

デスクトップ上で矩形選択した領域の情報を取得

slopを使えば実現できる。

% slop
979x749+4397+784

slopはクリックするとそのウィンドウの情報が得られる。この場合、ウィンドウボーダーを含む正確なウィンドウが得られる。 わかりやすく使えるから、

slop -c 0.2,0.3,1,0.7 -l

とかやると結構便利。

slopよりもxrectselのほうが有名なようだけど、これはちょっと複雑でlolilolicon版gvalkov版digitoronikによるPython版と多彩。 また、gvalkovのほうではslopを勧めている。

マウスポインタの位置を得る

xdotool getmouselocation

クリックポイントを得たい場合に有効。 今マウスポインタが乗っているウィンドウのIDも得られる。 --shellオプションを使うとevalできる形になる。

簡易画像認識

オートメーションしようと思うと割と状態識別の機能がいる。 特に画像認識をしたいことは多いだろう。

本格的な画像認識は難しいが、ソシャゲ程度なら割となんとかなる。

まずは状態を定義した画面を取得する。PNGかビットマップにしておくこと。

ffmpeg -f x11grab -video_size 1000x600 -i $DISPLAY+10,27 -frames:v 1 stateA.png

可変部分(ステータスの値や時間など)を塗りつぶす。

mogrify -fill black -draw "rectangle 20,50,150,150" stateA.png

これで準備完了。 判定する必要があるところでスクショを撮り、同じように塗りつぶす。

ffmpeg -y -f x11grab -video_size 1000x600 -i $DISPLAY+10,27 -frames:v 1 state-current.png
mogrify -fill black -draw "rectangle 20,50,150,150" state-current.png

ImageMagickを使って比較対象との状態を比べる。 SSIMによる比較は近似であるほど1に近づく。全く同じである場合はinfになる。 fuzzで少し削ってあげたほうがいいかもしれない。

diff=$(compare -fuzz 30% stateA.png state-current.png NULL:)
if [[ $diff == "inf" ]] || (( diff > 0.85 ))
then
  # 想定する画面が来ている場合の処理
  # ...
fi

日本語はまず無理だけど、英語(特に数値)に関してはOCRで認識できる可能性がある。

対象の画像を厳密に取得し、tesseractで抜き出す。

ffmpeg -y -f x11grab -video_size 224x21 -i $DISPLAY+460,120 -frames:v 1 state-current.png
value=$(tesseract state-current.png -)

日本語はtesseractよりはocrmypdfのほうがマシではあるけど、期待はしないほうがいい。

convert state-current.png state-current.pdf
ocrmypdf -l jpn state-current.pdf ocr.pdf
value=$(pdftotext ocr.pdf -)
rm ocr.pdf

ショートカットキーでコマンドを実行する

ウィンドウマネージャやデスクトップ環境にその機能があったりする。

それで足りない場合や、それが機能しない場合はxbindkeysを使うと良い。

xbindkeysについてはArchwikiに記事がある。

簡易で実践的な例

Enterキーによるメッセージ送りができない(マウスクリックで送る)ソシャゲでEnter送りを可能にする。ついでに、PgUp/PgDnをホイールに当てている。

Enterキーの入力はxevを使って拾っている。

クリックを要求するxdotool selectwindowの直後にxdotool getmouselocationを入れることで、クリックした座標を取得する、というテクニックを使っている。

このコードだと空行が入るので、単純に*)という条件を使うことはできない。

また、xevは別にデフォルトの挙動をキャンセルするわけではないので、これらのキーが何も効果を及ぼさないことを前提にしていることに注意してほしい。

#!/bin/zsh

winid=$(xdotool selectwindow)
eval $(xdotool getmouselocation --shell)

xev -id $winid | perl -00 -nl -e '$|=1;' -e '/XmbLookupString/ && /keycode ([^ ]+)/ && print $1;' | while read
do
  xdotool windowfocus $winid
  xdotool mousemove $X $Y
  case $REPLY in
    36)
      xdotool click 1
      ;;
    112)
      xdotool click 4
      ;;
    117)
      xdotool click 5
      ;;
  esac
done

まぁ、あんまりマウスホイールで履歴が見られるソシャゲもないので、それはそれで別に座標を設定してあげるとかすることになるだろうけども。

なお、わかりやすくいじりやすい題材だからソシャゲを持ってきたのであって、この記事を書くきっかけになったのはソシャゲではない。

普段テキスト送りをマウスでやっている人なら気にならないかもしれないが、個人的にはキーボードでテキスト送りができるととても快適。ソシャゲのストーリーを読もうかという気持ちが2割くらい増える。