GNOME Shell Extension を調べてみた
どうも、GNOME Seed の開発が停止し、 Gjs (GNOME JavaScript) のほうが活発に活動しているようです。GNOME Shell では Gjs が基本だし、拡張するためツールも Gjs だから、仕方がないのかな?
ということで、流れに逆らわないように Gjs の調査も兼ねて、 GNOME Shell Extension の作り方を調べてみました。
最初に、簡単なサンプルを作るコマンド gnome-shell-extension-tool を実行します。
$ gnome-shell-extension-tool --help Usage: gnome-shell-extension-tool [options] Options: -h, --help show this help message and exit -d DISABLE, --disable-extension=DISABLE Disable a GNOME Shell extension -e ENABLE, --enable-extension=ENABLE Enable a GNOME Shell extension -c, --create-extension Create a new GNOME Shell extension
-c オプションを付けると作成できるみたいです。
$ gnome-shell-extension-tool -c Name should be a very short (ideally descriptive) string. Examples are: "Click To Focus", "Adblock", "Shell Window Shrinker". Name: Sample Description is a single-sentence explanation of what your extension does. Examples are: "Make windows visible on click", "Block advertisement popups" "Animate windows shrinking on minimize" Description: This is my first sample extension. Uuid is a globally-unique identifier for your extension. This should be in the format of an email address (foo.bar@extensions.example.com), but need not be an actual email address, though it's a good idea to base the uuid on your email address. For example, if your email address is janedoe@example.com, you might use an extension title clicktofocus@janedoe.example.com. Uuid [Sample@your.pc.name]: sample@example.com Created extension in '/home/yourname/.local/share/gnome-shell/extensions/sample@example.com'
面倒なので、Sample という名前にし、 UUID を sample@example.com にしました。すると、$HOME/.local/share/gnome-shell/extensions 以下に sample@example.com というディレクトリができ、その中にファイルが3つ作成され、 gedit で extension.js が開かれます。
$ ls extension.js metadata.json stylesheet.css
中身を見る前に、どんなプログラムになるのか、試してみましょう。Alt + F2 でコマンド実行窓を開き、 "r" の一文字を入力して Enter キーを押します。すると、GNOME Shell が再起動し、上のパネルの時計の右側に、歯車のアイコンが表示されます。これをクリックすると、モニター中央に「Hello, world!」と表示され、徐々に薄くなっていくのが分かります。
ついでに、 LookingGlass https://live.gnome.org/GnomeShell/LookingGlass で確認しましょう。 Alt + F2 でコマンド実行窓を開き、"lg"の二文字を入力して Enter キーを押します。窓の左上に「Extensions」というのがあるので、それをクリックすると、今回作った Sample というのが見つかると思います。 LookingGlass の使い方は上のWebなどで調べて下さい。
では、extension.jsの中身を見て行きましょう。
const St = imports.gi.St; const Main = imports.ui.main; const Tweener = imports.ui.tweener;
最初の3行は GNOME Shell で利用されているオブジェクトを imports しています。
St を imports することで、/usr/lib/gnome-shell/St-1.0.typelib の中身が利用できるようになります。C言語のドキュメントは、 devhelp を使って確認できます。目次タブで「St Reference Manual」を見てください。本当なら、XML形式のgirファイルを調べれば良いのですが、gnome-shellをビルドするときに内部で作成されて、インストールはされないので、自分で作るしかないようです。
次の二行で、GNOME Shell の JavaScript で作成したライブラリを利用できるようにしています。 main.js と tweener.js は /usr/share/gnome-shell/js 以下に ui/main.js と tweener.js として存在しています。これらのマニュアルは無さそうなので、ソースを読まなきゃダメみたいですね。
さて、次の行を見ていきましょう。
let text, button;
この extension で利用するオブジェクトとして、 text と button を宣言しています。Gjs では var ではなく let を基本的に使ったほうが良いみたいです。text は、「Hello, world!」を表示する StLabel 型のオブジェクトです。button は、パネルに表示されている歯車アイコンのある StBin 型のオブジェクトです。実際に StBin はコンテナで、中にアイコンが配置されています。
function _hideHello() { Main.uiGroup.remove_actor(text); text = null; }
これは、 text という StLabel 型のオブジェクトを消す処理で、後で説明する _showHello() 関数の最後のアニメーション終了時に呼び出されます。最後に null を代入しているので、ガベージコレクションによってメモリからいずれ削除されます。
function _showHello() { if (!text) { text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" }); Main.uiGroup.add_actor(text); } text.opacity = 255; let monitor = Main.layoutManager.primaryMonitor; text.set_position(Math.floor(monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2)); Tweener.addTween(text, { opacity: 0, time: 2, transition: 'easeOutQuad', onComplete: _hideHello }); }
これは、 button という StBin 型のオブジェクトがクリックされた時に呼び出される _showHello() という名前のコールバック関数です。 text オブジェクトが null の場合、 text という名前の StLabel 型オブジェクトを作成し、Main.uiGroup の Actor として追加されます。 Actor というのは Clutter で使われるオブジェクトです。詳しくは Clutter のマニュアルを参照してください。
最初に text の不透明度を 255 にし、 Main.layoutManager.primaryMonitor オブジェクトを moniter 変数に割り当て、 text の一を monitor の中心に設定します。
そして、 Tweener を使ってアニメーションを実行します。内容は、 text オブジェクトの不透明度を徐々に透明にし、不透明度が0になったら _hideHello() 関数を呼び出します。
function init() { button = new St.Bin({ style_class: 'panel-button', reactive: true, can_focus: true, x_fill: true, y_fill: false, track_hover: true }); let icon = new St.Icon({ icon_name: 'system-run', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); button.set_child(icon); button.connect('button-press-event', _showHello); }
これは、 GNOME Shell が起動時に呼び出す初期化関数 init() です。最初に button という StBin 型のオブジェクトを作成し、次に icon という StIcon 型のオブジェクトを作成し、 button に icon を乗せ (子オブジェクトとして指定し)、button がクリックされた時のイベントハンドラ関数として _showHello() を指定しています。
注意するのは、 St.Bin のコンストラクタで、 reactive: true を指定しているところです。reactive は、St.Binが継承している親の StWidget の親にあたる ClutterActor クラスの property で、button がイベントに反応するかどうかを指定します。 true にすることで反応しますが、デフォルトは false なので必ず true にする必要があります。
function enable() { Main.panel._rightBox.insert_child_at_index(button, 0); } function disable() { Main.panel._rightBox.remove_child(button); }
これは、 gnome-tweak-tool の 「GNOME Shell 拡張」で ON/OFF された時に呼び出される関数です。GNOME Shell 起動時には必ず init() が呼び出されているので、 enable で button を追加し、 disable で button を削除します。
これを見る限り、有効にしない extension はメモリを利用し、スレッドを消費しているようなので、可能な限り削除したほうが良さそうですね。
ということで、GNOME Shell Extension を調べて、デフォルトで作成される extension.js のソースを見てきました。残りの2つのファイル metadata.json と stylesheet.css は、中を見れば簡単に解ると思います。なので、説明は省略します。
基本的に、 main.js で作成されているオブジェクトに、何らかの Actor を追加したり、イベントに対応して StWidget を用いて 独自 UI を作成したり、 tweener.js で記述されているアニメーション効果を利用する感じで作られているようです。後は、Gjs で記述できる処理なら何でもできそうな感じですね。
特に作りたい extension が無いので今回はここまでで終わりですが、今後のことも考えて Gjs の調査も進めていく予定です。