Emacs
Haskell
X11
Vivaldi
GTK+

keysnailなき後の世界を生きる

2017年11月のFirefox 57 "Quantum"のリリースによって、FirefoxをEmacs化するアドオンkeysnailが完全に動かなくなりました。今までも部分的に動かなくなったことはあったのですが、今度という今度は致命的だったようです。

ほとんどのkeysnail難民と同様、筆者もkeysnailなき後の世界を生き抜くための新たな環境構築を模索しました。最終的にそれなりに満足いくものになったのでその結果を共有します。

筆者のデスクトップ環境

  • Xubuntu Linux 16.04 (xenial)
  • xorg 1:7.7+13ubuntu3
  • XFCE 4.12.2

Vivaldi + Surfingkeys

keysnailがなくなった今となってはFirefoxに固執する理由はないため、keysnailのissueで情報提供のあったVivaldiとSurfingkeysアドオンの組み合わせを試してみました。

VivaldiはChromeベースのWebブラウザです。いろいろな特徴1がありますが、今回の件で特に有用だったのは、

  • ほぼ全てのキーボード・ショートカットをカスタマイズ可能。
  • ChromeベースなのでChrome向けアドオンをそのまま使える。

キーボード・ショートカットをカスタマイズすることで、忌まわしき「新しいウィンドウ(C-n)」「印刷(C-p)」も変更ないし解除できます。それだけでもかなりストレスが下がります。ただし、さすがにキーシーケンスに対するバインディングはできません。

そこで、さらにこれにSurfingkeysアドオンを挿します。

SurfingkeysはWebブラウザにvim風のキーバインドを加えるアドオンですが、keysnailと同様、JavaScriptコードで設定できるのが最大の特徴です。

デフォルトの挙動はvim風ですが、Emacsキーバインドを加えた設定を作りました。

Surfingkeysは設定スクリプトをHTTP(S)で直接読み込めるので、githubやgistに上げておくと便利です。

問題点

この時点でだいぶマシになったのですが、以下の問題がありました。

  • アドレスバーや検索バーの入力はSurfingkeysの効果が及ばない。
  • "404 NotFound"などのページではSurfingkeysの効果が及ばない。(バグかも?)

keysnailのスゴかった点は、アドレスバーだろうがメニューアイテムだろうが、さらにはダウンロードファイルの保存場所を決めるダイアログですらEmacsキーバインドにしてしまっていたところですが、Surfingkeysは(おそらくWebExtension APIの制約で)そこまでできないようです。

この問題に対処するために、Gtk+ 3の設定とX11レベルのキーリマップを行いました。

Gtk+ 3の設定

筆者の環境のVivaldiはGtk+ 3が使われているようだったので、アドレスバー、検索バー、Webページ上のテキストボックスなどはGtk+ 3の設定が効きます。

Gtk+ではテキスト入力ウィジェットのキーバインドをある程度カスタマイズできます。少なくともUbuntuのGtkパッケージにはデフォルトで"Emacs"テーマが入っているのですが、それを少々自分好みにカスタマイズしたキーバインドを作りました。

設定の適用方法は上記のREADMEを参照。

これにより、テキスト入力時のC-f, C-b, C-n, C-p, M-f, M-b, C-a, C-e, C-h, C-d, C-kなどが効くようになります。

X11レベルのキーリマップ

NotFoundページなどでSurfingkeysが機能しない問題については、ともかくタブ切り替えやタブ消去などの操作はちゃんとできてほしいところです。また、やや別件ですがC-mもあらゆる局面で機能してほしいところです。

ということで、これを解決するためにVivaldiの前段でキー入力イベントを変換(キーリマップ)するようにしました。キーリマップツールには既存のものがあるのですが、拙作のWildBindが既に似たような機能を持っていたのでこれを拡張しました。

キーリマップツールの比較

LinuxやX11環境で利用可能なキーリマップツールを以下の表にまとめます2

言語 入力源 キーシーケンス対応 任意コマンドの実行 任意処理の実行 参照ウィンドウ情報
xremap Ruby X11 × × WM_CLASS (res_name)
xkeysnail Python Linux Input Subsystem WM_CLASS (res_class)
WildBind Haskell X11 WM_CLASS (res_name, res_class), WM_NAME
  • 言語: 設定を記述するプログラミング言語。
  • 入力源: キー入力を検出するために使うシステム。
  • キーシーケンス対応: 複数のキー入力からなる入力列に対してバインドを設定できるか。
  • 任意コマンドの実行: 入力に応じて任意の外部コマンドを実行できるか。
  • 任意処理の実行: 入力に応じて任意の処理(関数など)を実行できるか。
  • 参照ウィンドウ情報: キーリマップ設定を切り替える際に参照する情報。いずれのツールも「アクティブウィンドウに応じてキーリマップ(バインド)を動的に変える」という機能を備えており、この情報を参照して設定切り替えの条件を設定できます。

WildBindはX11を入力源としているため、root権限なしで動作する一方、不自然と思われる挙動をする時があります。また、それなりに高機能ですが、そのぶん設定はややめんどくさいです。設定はHaskellプログラムとして書くのでめんどくささに拍車がかかっていますとってもtype-safeです。

WildBindの設定

Vivaldiに関する設定は以下のようにしました(全体の設定はこちらを参照)。

{-# LANGUAGE OverloadedStrings #-}

import Data.Monoid (mconcat)
import WildBind (Binding, whenFront)
import WildBind.Seq (toSeq, withPrefix, withCancel, fromSeq)
import WildBind.X11
  ( ActiveWindow, X11Front, XKeyEvent,
    winClass, ctrl, shift
  )
import WildBind.X11.Emulate (remap, remapR)
import WildBind.X11.KeySym  -- for xK_*

vivaldiKey :: X11Front i -> Binding ActiveWindow XKeyEvent
vivaldiKey x11 = whenFront condVivaldi binding
  where
    condVivaldi w = winClass w == "Vivaldi-stable"
    remap' = remap x11
    remap'' = remap x11
    remapR' = remapR x11
    binding = mconcat
              [ remap' (ctrl xK_n) (xK_Down),
                remap' (ctrl xK_p) (xK_Up),
                remap' (ctrl xK_s) (xK_slash),
                remapR' (ctrl xK_m) (xK_Return),
                remapR' (ctrl xK_g) (xK_Escape),
                fromSeq $ withCancel [ctrl xK_g] seq_binding
              ]
    seq_binding = mconcat [ withPrefix [ctrl xK_z] $ toSeq z_binding,
                            withPrefix [ctrl xK_x] $ toSeq x_binding
                          ]
    z_binding = mconcat [ remap'' (ctrl xK_n) (ctrl xK_Page_Down),
                          remap'' (ctrl xK_p) (ctrl xK_Page_Up),
                          remap'' (ctrl xK_c) (ctrl xK_t),
                          remap'' (ctrl xK_k) (ctrl xK_w),
                          remap'' (ctrl xK_slash) (shift $ ctrl xK_T)
                        ]
    x_binding = mconcat [ remap'' (ctrl xK_f) (ctrl xK_o),
                          remap'' (ctrl xK_s) (ctrl xK_s)
                        ]

この設定では、まずC-n, C-p, C-s, C-m, C-gをリマップしています。C-n, C-pをリマップするのは、アドレスバー入力時の候補選択で使えるようにするためです。

タブの操作はC-zプレフィクスにまとめています。また、「ファイルを開く」と「ファイルへ保存」もたまに使うのでC-xプレフィクスにバインドしました。

詳細なAPIはWildBind.SeqWildBind.X11.Emulateをご参照。

まとめ

自分の環境では

  • Vivaldi
  • Surfingkeys
  • Gtk+ 3
  • WildBind

の4つがかりの設定でようやくkeysnailなき後の世界を生き抜ける気がしてきました。改めてkeysnailの偉大さを思い知らされます。


  1. 今回の件とはあまり関係ないですが、タブの挙動を細かくカスタマイズ可能な点も良いです。執筆時点ではTab Mix PlusはQuantum未対応なので。。 

  2. なおWindows環境ではAutoHotkeyが圧倒的人気っぽいです。