[Ubuntu 18.04標準機能] gnome-shellの拡張機能を自作してみる (その1)

![ubuntu18.04_default_wallpaper.jpg](https://qiita-image-store.s3.amazonaws.com/0/171615/956e2798-2c31-0b96-7324-794a4ed94b1c.jpeg)

- [その1](https://qiita.com/Kogia_sima/items/cde2b550085e202a40a5)
- [その2](https://qiita.com/Kogia_sima/items/52ec628de987dd548a64)
- [その3](https://qiita.com/Kogia_sima/items/279c17d79656cbb7460d)

Ubuntu 18.04が正式にリリースされてから1ヶ月が経ちました。

Ubuntu 18.04からは、標準のデスクトップ環境がUnityからgnome-shellに変わりました。

しかしながら、このgnome-shell、あまりいい噂を聞きません。

- [gnome-shellを使うのをやめた](https://www.ncaq.net/2016/11/09/)
- [Umm, GNOME Shell Has a Rather Big Memory Leak](https://www.omgubuntu.co.uk/2018/03/gnome-shell-has-a-memory-leak-and-it-might-not-be-fixed-for-ubuntu-18-04-lts)
- [GNOME 3 Is Bad For Your Health As a Developer](https://blog.fpmurphy.com/2012/11/gnome-3-6-bad-for-your-health-as-a-developer.html)

実は私も、もう少しメモリ使用量を削減してほしいなーとか思っているのですが、それでもUnityと比較するなら、拡張性に優れたgnome-shellのほうが便利だと感じています。

# gnome-shellとは

デスクトップ環境が変わると言っても、初期の状態では見た目はUbuntu16.04の頃とほとんど変わりません。

個人的に大きな変更点だと思ったのは次の2点です。

- 標準のキーボードショートカットがUbuntu 16.04と異なり、またそれらを自分好みにカスタマイズできる
- gnome-shellの拡張機能を簡単にインストールできる！

拡張機能のインストールの仕方は驚くほど簡単です。好みのブラウザの拡張機能をインストールした後は、[拡張機能一覧](https://extensions.gnome.org/)のページで好きな機能を選んでボタンをクリックするだけ！

![Screenshot from 2018-05-29 19-13-14_k.png](https://qiita-image-store.s3.amazonaws.com/0/171615/5514f651-4ad3-e90c-5900-5039b9af6e0f.png)

ちなみに自分は合計で7個の拡張機能を入れています。あまり多く入れすぎるとデスクトップの立ち上がりに時間がかかるので程々に。。。

↓自分のデスクトップ環境はこんな感じになりました。

![Screenshot from 2018-05-29 19-06-50.png](https://qiita-image-store.s3.amazonaws.com/0/171615/0d8ed324-06d9-8795-1f87-7b6f2bd78217.png)

インストール済みの拡張機能:
- Dash to dock
- Icon hider
- Noannoyance
- Openweather
- User themes
- System-monitor
- Dropbox (自作)

Ubuntu 16.04の頃とはまるで別物のようです。

あ、デスクトップに黒文字で表示されている時計は、gnome-shellではなく[conky](https://github.com/brndnmtthws/conky)という独立型デスクトップウィジェットエンジンを使用しています。また暇ができたらこれについての記事も書く予定です。

gnome-shellでは自分の好きな拡張機能を入れることができるので、個々人の好みに合わせてデスクトップをカスタマイズすることができます。ここにいくつかのカスタマイズ例を紹介しておきます。

- [Ubuntu 16.04 LTSのGNOME ShellをMac風にカスタマイズする](https://ottan.xyz/ubuntu-gnome-shell-mac-high-sierra-6629/)
- [［Ubuntu 18.04 LTS］Windowsライクなデスクトップにしてみた【備忘録】](https://oompa-de-loompa.blogspot.jp/2018/05/ubuntu-1804-ltswindows.html)
- [Customizing Gnome 3 (Shell)](https://blog.fabianpiau.com/en/2012/03/15/customizing-gnome-3-shell/)

## 拡張機能の自作に踏み切ったワケ

gnome-shellの拡張機能一覧のページには結構な数の拡張機能が見つかるのですが、実はそのほとんどが現行のgnome-shellのバージョンに対応していません。
そのため、実質的にUbuntu 18.04で動く拡張機能の数というのは限られてきます。

そこで、より自分のニーズに合ったカスタマイズを作成するには拡張機能を自作しなければならなくなるわけですが、実際に自作する方法を調べても、全然記事がヒットしません。
自作するための導入部分に触れている記事はいくつか合ったのですが、実際にオリジナルの拡張機能を自作して、それを公開するまでのプロセスをしっかりと示している記事は残念ながら一つも見当たりませんでした。

そこでこの記事では、gnome-shellがインストール済みの状態を仮定し、gnome-shellの拡張機能を自作するまでの一連の過程を紹介したいと思います。

# 今回作るもの

今回は、Dropboxの同期状態をトップバーに表示するような拡張機能を自作したいと思います。

![dropbox_ext.png](https://qiita-image-store.s3.amazonaws.com/0/171615/26258f1d-3bef-87cd-a4df-717d08209317.png)

## 事前にインストールするもの

- [DropboxのLinux向け公式クライアント](https://www.dropbox.com/ja/install-linux)

Dropboxのクライアントを使用したことがない方向けに軽く使い方を説明しておくと、

- `dropbox start`で同期開始
- `dropbox stop`で同期中断
- `dropbox running`で同期状態を取得(0 or 1)

という感じで使います。

実はこのソフトをインストールするとDropboxの同期状態がトップバーに表示されるのですが、なぜか表示されないときが合ったり、表示されても表示が間違っていたりなど、やや不具合があるようです。
そこで、正しい状態を表示するために、gnome-shellの拡張機能を自作します。

# 準備

まずは、gnome-shellに付属しているツールを使って、拡張機能の雛形を作ります。

```console
$ gnome-shell-extension-tool --create-extension

Name should be a very short (ideally descriptive) string.
Examples are: "Click To Focus",  "Adblock", "Shell Window Shrinker".

Name: dropbox

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: Dropbox status indicator

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 [Dropbox@dynabook-T55]:
```

ここで、拡張機能の基本となる設定を行います。各質問項目の内容は以下のとおりです。

- Name: 拡張機能の名前
- Description: 拡張機能の説明 (1行)
- Uuid: 拡張機能の識別子

Uuidの値は基本的に自由ですが、後のデバッグのためにも、「<拡張機能の名前>@<メールアドレスの@を.に変えたもの>」としておくのが無難です。

設定が終わると、$XDG_DATA_HOME/gnome-shell/extensionsというディレクトリ内に、先程のUuidで指定した名前のディレクトリが生成されます。

```console
$ ls -lF $XDG_DATA_HOME/gnome-shell/extensions/dropbox@kogia-sima.example.com
total 20K
drwxr-xr-x  2 kogia-sima kogia-sima 4.0K May 29 20:46 ./
drwxrwxr-x 12 kogia-sima kogia-sima 4.0K May 29 20:46 ../
-rw-r--r--  1 kogia-sima kogia-sima 1.5K May 29 20:46 extension.js
-rw-r--r--  1 kogia-sima kogia-sima  106 May 29 20:46 metadata.json
-rw-r--r--  1 kogia-sima kogia-sima  172 May 29 20:46 stylesheet.css
```

拡張子を見ると、.jsや.cssなど、いずれもwebページでよく見かけるような拡張子が並んでいますね。

そう、gnome-shellは、**webページを編集するのと同じようなイメージで拡張機能を作れてしまいます。**

これはWeb系のエンジニアからすればとても嬉しいでしょうし、またJavaScriptは様々な場面で利用されているため、多くの人にとってもゼロから学ぶよりはかなりのストレスを軽減できるのではないでしょうか。

## デバッグ環境の立ち上げ

拡張機能の開発を行う際には、常にデバッグ環境を立ち上げてエラーの内容を表示するようにしておくと良いでしょう。

今の状態からもう一つ端末を開くか、gnome-terminalの場合はCtrl+Shift+Tで別タブに別セッションを生成します。

そのセッションで、以下のコマンドを実行してください。

```
sh -c 'gnome-shell --replace 2>&1 |grep <拡張機能の名前> -A 2'
```

これにより、もともと動いていたgnome-shellプロセスが閉じられ、新たに端末内でプロセスが生成されます。つまり、標準出力や標準エラー出力の内容が端末に表示されるようになります。

**このセッションは、ログアウトするまで閉じないでください。**

gnome-shellのプロセスが中断されてしまうと、キーボード操作が反応しなくなり、ログアウトもできなくなり、また場合によってはシャットダウンも実行できなくなります。絶対にセッションを閉じないでください。

nohupコマンドとか使えばそのような心配いらないのかもしれないけど使い方がよく分かっていない。

## 雛形を動かしてみる

以下のコマンドを実行し、作成した雛形を有効にしてください。有効にするとすぐに、トップバーに歯車のアイコンが表示されます。

```console
$ gnome-shell-extension-prefs
```

歯車のアイコンをクリックすると、画面中央に`Hello, world!`と表示されます。

![Screenshot from 2018-05-29 21-03-48.png](https://qiita-image-store.s3.amazonaws.com/0/171615/9fff928d-9504-49bd-4d1f-fef0f25ebac7.png)

## 雛形のソースコードを見る

それでは、ソースコードがどのようになっているのか見てみます。

```javascript:extension.js

const St = imports.gi.St;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

let text, button;

function _hideHello() {
    Main.uiGroup.remove_actor(text);
    text = 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(monitor.x + Math.floor(monitor.width / 2 - text.width / 2),
                      monitor.y + Math.floor(monitor.height / 2 - text.height / 2));

    Tweener.addTween(text,
                     { opacity: 0,
                       time: 2,
                       transition: 'easeOutQuad',
                       onComplete: _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-symbolic',
                             style_class: 'system-status-icon' });

    button.set_child(icon);
    button.connect('button-press-event', _showHello);
}

function enable() {
    Main.panel._rightBox.insert_child_at_index(button, 0);
}

function disable() {
    Main.panel._rightBox.remove_child(button);
}
```

最初のimportsの部分では、必要なモジュールを読み込んでいます。Node.jsやPHPなどでいうrequireとほぼ同じ役割だと思ってもらって構いません。

init関数は、gnome-shellのプロセスが生成された直後に呼ばれる関数で、その後にenable関数が呼ばれます。

またenable関数は拡張機能が有効になったときにも呼ばれます。そのため、ボタンの表示などUIに関する設定はこの関数内で行うことになります。

disable関数は、拡張機能が無効になった時、あるいはgnome-shellプロセスが終了する時に呼ばれます。

本当はこの雛形を解説してもいいのですが、今回の拡張機能ではこれらのコードをほとんど利用しませんので、細かな説明は省かせていただきます。

雛形コードの詳細な説明が知りたい方は、以下のページが参考になると思います。

[http://gihyo.jp/admin/serial/01/ubuntu-recipe/0492?page=2](http://gihyo.jp/admin/serial/01/ubuntu-recipe/0492?page=2)

# 拡張機能の仕様を決める

どんなソフトウェア開発に置いても、要件定義と仕様書の作成はきちんと行いましょう。

今回の拡張機能では、

- Dropboxのローカルフォルダがリモートと同期されているときはトップバーにDropboxのアイコンを表示し、同期されていないときは非表示にする。
- トップバーにあるDropboxのアイコンをクリックすると、'Open folder'と'Turn off'という2つのボタンを含むパネルメニューが出現する。
- 'Open folder'ボタンをクリックすると、ローカルの同期先ディレクトリを開く。
- 'Turn off'というボタンをクリックすると、Dropboxの同期を中断する

ということを目指します。

# TO BE CONTINUED...

続く -> [その2](https://qiita.com/Kogia_sima/items/52ec628de987dd548a64)