Super キーで「全てのアプリケーション」の表示・非表示のみをトグルして Activities Overview 画面を封じる GNOME Shell 拡張機能を書いた
Ubuntu 18.04 にて。既存の GNOME Shell 拡張機能を書き換えることで、自分が求める動作を実現させた。
目次
何が問題だったか
Ubuntu のデフォルトだと、画面左上の「アクティビティ」を押下すると表示される、ウィンドウとワークスペースの一覧画面。MacOS の Mission Control みたいなヤツ。この画面を「Activities Overview」画面と呼ぶそうなのだが、存在から抹消したい。
自分はキーボード・ショートカットを色々変更して、アクティビティ系の表示設定は全て無効化し、PrintScreen キーに Show all applications
を割り当てている (デフォルトは Super + A
)。コレで PrintScreen キーを押すと全てのアプリケーション一覧が表示されるのだが、もう一度 PrintScreen を押すと、アプリケーション一覧が閉じるのではなく、「Activities Overview」画面が開いてしまうのだ。「All Applications」と「Activities Overview」を行き来してしまうので、閉じるには Esc キーを押さないといけない。
「アクティビティ」アイコンを消す系の GNOME Shell 拡張機能は多数あるのだが、この「Show all applications」からの「Activities Overview」画面への移動を抑止するような仕組みは、探しても見つからなかった。できれば Windows における Win キー単独の押下みたいな感じで、PrintScreen キーの押下で「全てのアプリケーション一覧」の表示 ⇔ 非表示をトグル切り替えする動きをしてほしいと思っている。
- 参考 : 18.04 - Keyboard shortcut to "Show Applications" - Ask Ubuntu
- 参考 : gnome shell - How do I disable showing the "Activities overview" forever? - Ask Ubuntu
ところで、ESC to close overview from applications list という GNOME Shell 拡張機能がある。「Activities Overview」か「All Applications」画面で Esc キーを押すと、この画面を閉じられるというモノだ。
この GNOME Shell 拡張機能を参考に、やりたいことが実現できるのではないかと思い、書いてみることにした。
既存の GNOME Shell 拡張機能を書き換える
というワケで実装。
GNOME Shell 拡張機能は JavaScript で書かれており、インストールした拡張機能は以下のディレクトリに存在した。
~/.local/share/gnome-shell/extensions/ESC_to_close_overview@daniel.badawi.me/
この配下に extension.js
というファイルがある。
extension.js
を次のように直す
この extension.js
を次のように修正した。全文を貼り付ける。
const Clutter = imports.gi.Clutter;
const Main = imports.ui.main;
const Lang = imports.lang;
// 以下2行を追加
const { Gio, Meta, Shell } = imports.gi;
const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
let _this;
let escAction;
//based on https://github.com/GNOME/gnome-shell/blob/gnome-3-20/js/ui/viewSelector.js
function esc(actor, event) {
_this = this;
if (Main.modalCount > 1)
return Clutter.EVENT_PROPAGATE;
let modifiers = event.get_state();
let symbol = event.get_key_symbol();
// 以下条件を追加
if (symbol == Clutter.Escape || symbol == Clutter.Print || symbol == Clutter.KEY_3270_PrintScreen || symbol == Clutter.Super_L || symbol == Clutter.Super_R) {
return escAction();
} else if (this._shouldTriggerSearch(symbol)) {
this.startSearch(event);
} else if (!this._searchActive && !global.stage.key_focus) {
if (symbol == Clutter.Tab || symbol == Clutter.Down) {
this._activePage.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
return Clutter.EVENT_STOP;
} else if (symbol == Clutter.ISO_Left_Tab) {
this._activePage.navigate_focus(null, Gtk.DirectionType.TAB_BACKWARD, false);
return Clutter.EVENT_STOP;
}
}
return Clutter.EVENT_PROPAGATE;
}
function init() {
// ↓ 追加ココカラ
Main.wm.removeKeybinding('toggle-application-view');
Main.overview.viewSelector._toggleAppsPage = function() {
Main.overview.viewSelector._showAppsButton.checked = true;
if(Main.modalCount >= 1) {
Main.overview.hide();
}
else {
Main.overview.show();
}
};
Main.wm.addKeybinding('toggle-application-view',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.NONE,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
Main.overview.viewSelector._toggleAppsPage.bind(Main.overview.viewSelector));
// ↑ 追加ココマデ
Main.overview.viewSelector._onStageKeyPress = esc;
}
function originalEscAction() {
if (_this._searchActive)
_this.reset();
else if (_this._showAppsButton.checked)
_this._showAppsButton.checked = false;
else
Main.overview.hide();
return Clutter.EVENT_STOP;
}
function modifiedEscAction() {
if (_this._searchActive)
_this.reset();
else
Main.overview.hide();
return Clutter.EVENT_STOP;
}
function enable() {
escAction = modifiedEscAction;
}
function disable() {
escAction = originalEscAction;
}
Diff で表現
diff
で表現すると次のとおり。
$ diff --color=auto ORIG-extension.js extension.js
4a5,8
> // 以下2行を追加
> const { Gio, Meta, Shell } = imports.gi;
> const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
>
17c21,22
< if (symbol == Clutter.Escape) {
---
> // 以下条件を追加
> if (symbol == Clutter.Escape || symbol == Clutter.Print || symbol == Clutter.KEY_3270_PrintScreen || symbol == Clutter.Super_L || symbol == Clutter.Super_R) {
33a39,57
> // ↓ 追加ココカラ
> Main.wm.removeKeybinding('toggle-application-view');
> Main.overview.viewSelector._toggleAppsPage = function() {
> Main.overview.viewSelector._showAppsButton.checked = true;
> if(Main.modalCount >= 1) {
> Main.overview.hide();
> }
> else {
> Main.overview.show();
> }
> };
> Main.wm.addKeybinding('toggle-application-view',
> new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
> Meta.KeyBindingFlags.NONE,
> Shell.ActionMode.NORMAL |
> Shell.ActionMode.OVERVIEW,
> Main.overview.viewSelector._toggleAppsPage.bind(Main.overview.viewSelector));
> // ↑ 追加ココマデ
>
どうやって反映する?
extension.js
を書き換えただけではこのコードは反映されない。
Alt + F2
キーを押して「コマンドを入力」を表示し、r
とのみ入力して実行する。
すると画面中央に「再起動中…」というポップアップが表示され、GNOME Shell 拡張機能を再読込できる。
コレで書き換えた extension.js
が有効になる。PrintScreen キーを押せば「全てのアプリケーション一覧」が開き、もう一度 PrintScreen キーを押せば一覧が閉じる。閉じるキーには Esc キーの他に Super キーなども含めておいた。
何をどう実装しているのか
初めて GNOME Shell 拡張機能とやらを触ってみたワケだが、どうやって実装したかメモを残しておく。
まず、言語的には JavaScript がベースなので、処理はなんとなく追いやすかった。import
しているソース群は、ググってみると以下にあったので、参考にした。
初めに、if(symbol == Clutter.Escape)
部分が気になったので、Clutter
をググり、キーボードごとの定数があることを突き止め、条件を増やしてみた。しかし、PrintScreen キーを押下してもこの条件文には到達していないようだったので、もう少し調べてみた。
ViewSelector
クラスの実装を見てみると、何やら Overview 絡みのキーイベントを定義しているっぽいところを見つけた。
- gnome-shell/viewSelector.js at 9c1940ef9db50ee693c2f82c8a2a4486bce1a1fe · GNOME/gnome-shell · GitHub
Main.wm.addKeybinding('toggle-application-view',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._toggleAppsPage.bind(this));
Main.wm.addKeybinding('toggle-overview',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
Main.overview.toggle.bind(Main.overview));
addKeybinding()
があるなら remove
もあるかな?と思い調べてみたら、あった。
Main.wm.removeKeybinding('custom-switch-is-key');
というワケで、まずはイベントを remove
だけして様子を見てみると、toggle-application-view
イベントを消したらアプリケーション一覧が開かなくなったので、コイツがキモだと分かった。
コードを見ると、_toggleAppsPage()
関数で何やらチェックボックス的なモノを操作しつつ、Overview を表示しているっぽかった。
- gnome-shell/viewSelector.js at 9c1940ef9db50ee693c2f82c8a2a4486bce1a1fe · GNOME/gnome-shell · GitHub
_toggleAppsPage() {
this._showAppsButton.checked = !this._showAppsButton.checked;
Main.overview.show();
}
_showAppsButton
の checked
を切り替えることに意味があるのか?と思いコードを追うと、以下の行を発見。どうやらチェック状況によって別の関数を呼び出すイベントが定義されているようだった。
- gnome-shell/viewSelector.js at 9c1940ef9db50ee693c2f82c8a2a4486bce1a1fe · GNOME/gnome-shell · GitHub
this._showAppsButton.connect('notify::checked', this._onShowAppsButtonToggled.bind(this));
_toggleAppsPage()
関数のすぐ下にある showApps()
関数も参考にすると、
_showAppsButton.checked
をtrue
にすれば、「Activities Overview」ではなく「アプリケーション一覧」が開くMain.overview.show()
かhide()
で、「Activities Overview」または「アプリケーション一覧」の画面全体を表示・非表示できる
ということが分かった。
そしたら、既存のイベントを一旦 remove
し、オレオレイベントを再定義してやることにしよう。それが init()
関数に追記した以下のコードだ。
const { Gio, Meta, Shell } = imports.gi;
const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
// 以下 init() 関数内
Main.wm.removeKeybinding('toggle-application-view');
Main.overview.viewSelector._toggleAppsPage = function() {
Main.overview.viewSelector._showAppsButton.checked = true;
if(Main.modalCount >= 1) {
Main.overview.hide();
}
else {
Main.overview.show();
}
};
Main.wm.addKeybinding('toggle-application-view',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.NONE,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
Main.overview.viewSelector._toggleAppsPage.bind(Main.overview.viewSelector));
import
は以下を参考にした。
- gnome-shell/viewSelector.js at 9c1940ef9db50ee693c2f82c8a2a4486bce1a1fe · GNOME/gnome-shell · GitHub
既存イベントを remove
したら _toggleAppsPage()
関数をオレオレ関数に書き換える。Main.modalCount
を見ると、「Activities Overview」か「アプリケーション一覧」が開いているかどうかが分かるので、開いていれば hide()
、開いていなければ show()
を呼ぶようにした。
addKeybiding()
中で、Meta.KeyBindingFlags
を使用しているところがある。元々のコードでは Meta.KeyBindingFlags.IGNORE_AUTOREPEAT
という定数を使用していたが、どうもこの定数が見当たらなかったので、代わりにテキトーに NONE
を指定してみることにした。
init()
関数あたりに print('META : ' + JSON.stringify(Meta.KeyBindingFlags) );
といったコードを入れ、Alt + F2
→ r
で GNOME Shell を再起動、その後ターミナルで
$ journalctl /usr/bin/gnome-shell
の最後の方を見ると、次のようなログが見えるはずだ。IGNORE_AUTOREPEAT
は存在しないみたい。
実装時は init()
関数内を try / catch
で囲み、エラーを print(e)
で出力することで、Meta.KeyBindingFlags.IGNORE_AUTOREPEAT
が存在しないことを突き止められた。
2月 20 12:32:44 Neos-ThinkPad org.gnome.Shell.desktop[1143]: Error: Expected type flags for Argument 'flags' but got type 'undefined'
こんな感じのエラーが出ていた。
テキトーに NONE
を指定してやったら上手く行ったので、コードを整理して終わり。上で示した完成形となった。
まとめ
- GNOME Shell 拡張機能は
~/.local/share/gnome-shell/extensions/
配下に保存される extension.js
を編集後、Alt + F2
→r
で GNOME Shell を再起動すると、extension.js
の内容が再読込されるprint()
関数を使うと、$ journalctl /usr/bin/gnome-shell
にログが出力できる- Print Debug しながら、スクリプトを編集し再読込していくことで、なんとか実装できていく
ということで、GNOME Shell 拡張機能を書いてみた。今回は既存のどこぞの拡張機能のソースコードを手直しする形で実装してみたけど、コマンドラインツールでゼロから作って、拡張機能ストアに登録することもできるようなので、今後試してみたい。