X11シェル芸

この記事はシェル芸 Advent Calendar 2019 12日目の記事です。 当初 Qiita に投稿していましたが、こちらに引っ越しました。

X11シェル芸とは

X Window System(X11)上のGUIアプリケーションを操作するシェル芸です。 こんなやつ。

デスクトップ版のLinuxディストリビューションxeyesを実行すると、マウスカーソルを追いかける目玉のアプリケーションが動きます。 このようなGUIアプリケーションを、シェル芸botのような、デスクトップのない環境で実行しようというチャレンジです。

準備

Ubuntuで、以下のパッケージをインストールします。

  • xvfb
    • X11の仮想フレームバッファを提供するコマンドで今回の主役
    • 物理的なディスプレイがなくても、コイツとメモリがあればマルチディスプレイも余裕だ
  • x11-apps
    • xeyesなどの楽しいX11アプリケーション詰め合わせ
  • x11-utils
    • X11環境を便利に使うツールのお得な詰め合わせ
    • ここではxwininfoを使う
  • xdotool
  • 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形式のフォーマットで出力されますが、ImageMagickconvertで処理することができます。

# xwd -display :1 -root | convert xwd:- /images/xeyes.png

Dockerホストの./images/xeyes.pngに次のような画像が生成されていると思います。 f:id:horo1717:20200326012054p:plain

ウィンドウを操作する

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

f:id:horo1717:20200326012200p:plain

シェル芸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環境もコマンドラインから扱えると、作業が捗りそうです。 楽しいシェル芸ライフを。