Gnome Shell (やそれからフォークされた Cinnamon など) の、アプレットなどの目に見える部品の多くは JavaScript で作られている。 これは GJS なる Gnome の JavaScript バインディングをつかって書かれている。 JavaScript から Clutterを叩いている。 Clutter を介した描画処理自体は OpenGL を使って描画されるので高速に処理されることになっている。

しかし、実際に触ったことはなかったので、お試しで時計をつくることで触ってみた。

時計仕様

時計は、現在時刻と、引数で指定した時刻の差分をリアルタイム表示することとした。 また Clutter の OpenGL 描画を活かすために引数で指定した時刻の差分が60秒以下の場合は赤く点滅するようにした。

これをつくる経緯としては、とあるライブのDJセットにて、現在時刻、ストップウォッチ(開始からの時間)、カウントダウン(終了までの残り時間) の3段表示のデジタルクロックを見かけたことである。 そして、これは普通にDJとか関係なく便利そうだと思ったことにある。 その動画は今は見つからなくなってしまったが、HTML上で再現するとおおよそ下記のようになる:

88:88:88.88
88:88:88.88
88:88:88.88
01:23:45.78
00:00:00.00
02:00:00.00
デジタルクロックの再現図

成果物

https://gist.github.com/cat-in-136/64759185fc206fa4c8cdf49ec4de361e

gjsをインタプリタとして実行する。shebang (#!)も指定しているので実行権限を付与して実行しても良い。

$ gjs path/to/djclock.js
djclock screen capture

メモ

Clutter.Stageがメインウィンドウである。

const stage = new Clutter.Stage({
  title: "djclock",
  layout_manager: new Clutter.BoxLayout(),
  user_resizable: true,
  x_expand: true,
  y_expand: true,
  background_color: Clutter.Color.from_string("#000")[1],
});
stage.set_size(256, (1+TIMES.length)*24);
stage.connect("destroy", () => Clutter.main_quit());

Clutter自体は簡素な部品と簡素なレイアウトマネージャがあるため、簡単な配置はこれで十分である。 ボタンなど高級な部品は提供されていないので、そういうのが必要なときは何らかの方法で頑張る必要があるが、 今回はテキストを表示するだけであるのでこれで十分である。

const box = new Clutter.Box({
  layout_manager: new Clutter.BoxLayout({
    orientation: Clutter.Orientation.VERTICAL,
    spacing: 2,
  }),
  x_expand: true,
});
stage.add_child(box);

const time_current = new Clutter.Text({
  x_expand: true,
  text: '...',
  color: Clutter.Color.from_string("#fff")[1],
});
box.add_child(time_current);

点滅アニメーションは予めアニメーションを登録して、それをClutter.Textにセットすればよい。 点滅処理自体はClutter自身が面倒を見る。ここはCSS Animation的な思想に近い。

const pt_60sec_before = new Clutter.PropertyTransition({ property_name: 'background-color' });
pt_60sec_before.set_from(Clutter.Color.from_string("#000")[1]);
pt_60sec_before.set_from(Clutter.Color.from_string("#800")[1]);
pt_60sec_before.set_progress_mode(Clutter.AnimationMode.LINEAR);
const tg_60sec_before = new Clutter.TransitionGroup();
tg_60sec_before.set_duration(1000);
tg_60sec_before.set_repeat_count(-1);
tg_60sec_before.add_transition(pt_60sec_before);
const TG_NAME_60SEC_BEFORE = "60sec-before";

// ...snip...

times_dj[i].remove_all_transitions();
times_dj[i].add_transition(TG_NAME_30SEC_BEFORE, tg_30sec_before);

その他はClutterに関係ない純粋なJavaScriptの知識で解せる内容ではあるが、 SpiderMonkey由来ということもあり普通にモダンなJavaScriptがかけるというのはいいことだ。 2011年のGnome Shell登場時はまだ使えない文法も多かったはずで、かなり面倒くさい書き方をしていたのではないかと想像する。

const text = [
  (diff < 0)? "-" : "+",
  (days != 0)? `${days} ` : "",
  `${hours}`.padStart(2, "0"),
  ":",
  `${minutes}`.padStart(2, "0"),
  ":",
  `${seconds}`.padStart(2, "0"),
  ".",
  `${microseconds}`.padStart(6, "0"),
].join("");
times_dj[i].set_text(text);

あとClutterの情報は日本語はもちろん英語も資料はすくない。 Gnome Shellの拡張に関する文献とClutterのAPIを参考するのが一番近道だ。 ふとしのブログにいくつか記事があって参考になるが、 ソースコードが古いので手を加える必要がある。

参考文献