X11シェル芸
この記事はシェル芸 Advent Calendar 2019 12日目の記事です。 当初 Qiita に投稿していましたが、こちらに引っ越しました。
X11シェル芸とは
X Window System(X11)上のGUIアプリケーションを操作するシェル芸です。 こんなやつ。
https://t.co/DGP40W3QKu pic.twitter.com/R5C2UJh0eE
— シェル芸bot (@minyoruminyon) 2019年8月18日
デスクトップ版のLinuxディストリビューションでxeyes
を実行すると、マウスカーソルを追いかける目玉のアプリケーションが動きます。
このようなGUIアプリケーションを、シェル芸botのような、デスクトップのない環境で実行しようというチャレンジです。
準備
Ubuntuで、以下のパッケージをインストールします。
- xvfb
- x11-apps
xeyes
などの楽しいX11アプリケーション詰め合わせ
- x11-utils
- X11環境を便利に使うツールのお得な詰め合わせ
- ここでは
xwininfo
を使う
- xdotool
- コマンドラインからマウス・キーボード・ウィンドウを操作するツール
- 第41回シェル芸勉強会のLTで@s_mituさんが紹介されていた
- imagemagick
- 画像処理なら任せろ
ウィンドウマネージャ等は必要ないので省きます。 Docker環境で試せるよう、Dockerfileを用意しておきました。
FROM ubuntu:19.10 RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive \ apt-get install -y --no-install-recommends \ xvfb x11-apps x11-utils xdotool imagemagick \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*
Dockerfile
というファイル名で保存し、次のようなコマンドでDockerイメージをビルドできます。
$ docker image build -t shellgeiadv .
Dockerコンテナを起動します。実行するGUIアプリケーションのスクリーンショットを出力するため、./images
をボリュームマウントしておきます。
$ docker container run -it --rm -v $(pwd)/images:/images shellgeiadv
仮想フレームバッファを準備する
この状態でxeyes
を実行すると、ディスプレイが開けねーぞって怒られます。そりゃあそうです。
# xeyes Error: Can't open display:
なので、Xvfbを使って、仮想的なディスプレイを用意してやります。
# Xvfb :1 -screen 0 320x240x8 & [1] 8
:1
はディスプレイサーバーの番号、-screen
はディスプレイサーバーに紐付くスクリーン番号(0
)の解像度(幅:320 x 高さ:240)x 色深度(8bit)の指定です。バックグラウンドで起動するため、&
を付けています。
こんな極小ディスプレイはほとんどないと思いますが、仮想ディスプレイなら簡単に作れちゃいます。
X11アプリケーションは、DISPLAY
変数に設定されたディスプレイサーバー番号にウィンドウを表示するため、Xvfbで作成した仮想ディスプレイの番号(:1
)を設定しておきます。
# export DISPLAY=:1
xeyesを実行する
こちらもバックグラウンドで実行しておきます。
# xeyes & [2] 11
ターミナルには何も表示されないため、動いているのかよくわかりませんが、ps
でプロセスを確認すると、ちゃんと動いているようです。
# ps PID TTY TIME CMD 1 pts/0 00:00:00 bash 8 pts/0 00:00:00 Xvfb 11 pts/0 00:00:00 xeyes 12 pts/0 00:00:00 ps
xwininfo
を使うと、仮想ディスプレイサーバーに配置されたウィンドウの位置が、なんとなくわかります。
# xwininfo -display :1.0 -root -tree xwininfo: Window id: 0x4d (the root window) (has no name) Root window id: 0x4d (the root window) (has no name) Parent window id: 0x0 (none) 1 child: 0x20000a "xeyes": ("xeyes" "XEyes") 150x100+0+0 +0+0 1 child: 0x20000b (has no name): () 150x100+0+0 +1+1
画面をキャプチャする
xwd
(X Window System window dumping utility)を使って、ディスプレイのスクリーンキャプチャを取得することができます。
XWD形式のフォーマットで出力されますが、ImageMagickのconvert
で処理することができます。
# xwd -display :1 -root | convert xwd:- /images/xeyes.png
Dockerホストの./images/xeyes.png
に次のような画像が生成されていると思います。
ウィンドウを操作する
xdotool
を使ってウィンドウを移動してみます。xdotool
でウィンドウを操作するには、ウィンドウIDを取得する必要があります。先のxwininfo
より、xeyes
のウィンドウIDは0x20000a
と表示されましたが、環境によって変わる可能性があるため、xdotool search
を使って、ウィンドウ名で検索した結果を利用します。
# xdotool search --name xeyes 2097162
xeyes
のウィンドウサイズは、先のxwininfo
より、150x100となります。
また、xdotool
で次のように取得することもできます。
# xdotool getwindowgeometry 0x20000a Window 2097162 Position: 0,0 (screen: 0) Geometry: 150x100
これらの座標を元に適当に計算し、ウィンドウをディスプレイの中心に配置してみます。
# xdotool search --name xeyes | xargs xdotool getwindowgeometry | awk -F'[x ]' 'NR==1 {printf $2" "} NR==3 {print (320-$4)/2, (240-$5)/2}' | xargs xdotool windowmove
スクリーンキャプチャを取得してみると、寄り目が現れます。
# xwd -display :1 -root | convert xwd:- /images/xeyes_center.png
シェル芸botで実行する
ここまでの内容を、適当にスクリプトとして実行します。バックグラウンドで動作するコマンドを連続して実行すると、前のコマンドの実行が間に合わないことがあるため、適当にsleep
を挟んでおくと良いでしょう。
Xvfb :1 -screen 0 320x240x8 & sleep 1 export DISPLAY=:1 xeyes & sleep 1 xdotool search --name xeyes \ | xargs xdotool getwindowgeometry \ | awk -F'[x ]' 'NR==1 {printf $2" "} NR==3 {print (320-$4)/2, (240-$5)/2}' \ | xargs xdotool windowmove & sleep 1 xwd -display :1 -root | convert xwd:- /images/x.png
シェル芸botは、バックグラウンドプロセスが動きっぱなしでも実行結果ツイートしてくれますが、SGWebで実行する場合は、バックグラウンドプロセスも終了させておく必要があるため、timeout
コマンドで指定秒数後に停止するよう仕込んでおくと良いでしょう。
timeout 5 Xvfb :1 -screen 0 320x240x8 & sleep 1 DISPLAY=:1 timeout 3 xeyes & sleep 1 xdotool search --name xeyes \ | xargs xdotool getwindowgeometry \ | awk -F'[x ]' 'NR==1 {printf $2" "} NR==3 {print (320-$4)/2, (240-$5)/2}' \ | xargs xdotool windowmove & sleep 1 xwd -display :1 -root | convert xwd:- /images/x.png
おわりに
シェル芸botでxeyesを表示するだけではあまり面白くないですが、GUI環境をコマンドラインから扱えると、ちょっと嬉しいことがあるかもしれません。たとえば、GUIアプリケーションをデスクトップに並べてパズルとして遊ぶとか。そういえば昔、LinuxサーバーにXvfbとLibreOfficeを入れて、sofficeコマンドを使ってヘッドレスでOfficeドキュメントをPDFに変換していたこともありましたね。そんな感じで、GUI環境もコマンドラインから扱えると、作業が捗りそうです。 楽しいシェル芸ライフを。