Linuxで思い通りのキーマップを設定する【xkeysnail】


LinuxのキーマップはWindowsのそれに近いので、Mac使いにすれば少々使いにくいです。そこでPython製のツールであるxkeysnailを使用して独自のキーマップを設定してみました。いつでもVim風にhjklでカーソル移動できるようになったのでかなり快適。インストールと設定そしてちょっと面倒な自動起動の方法についてまとめます。

xkeysnail

mooz/xkeysnai

from xkeysnail.transform import *

define_keymap(re.compile("Firefox|Google-chrome"), {
    # Ctrl+Alt+j/k to switch next/previous tab
    K("C-M-j"): K("C-TAB"),
    K("C-M-k"): K("C-Shift-TAB"),
}, "Firefox and Chrome")

define_keymap(re.compile("Zeal"), {
    # Ctrl+s to focus search area
    K("C-s"): K("C-k"),
}, "Zeal")

define_keymap(lambda wm_class: wm_class not in ("Emacs", "URxvt"), {
    # Cancel
    K("C-g"): [K("esc"), set_mark(False)],
    # Escape
    K("C-q"): escape_next_key,
    # C-x YYY
    K("C-x"): {
        # C-x h (select all)
        K("h"): [K("C-home"), K("C-a"), set_mark(True)],
        # C-x C-f (open)
        K("C-f"): K("C-o"),
        # C-x C-s (save)
        K("C-s"): K("C-s"),
        # C-x k (kill tab)
        K("k"): K("C-f4"),
        # C-x C-c (exit)
        K("C-c"): K("M-f4"),
        # cancel
        K("C-g"): pass_through_key,
        # C-x u (undo)
        K("u"): [K("C-z"), set_mark(False)],
    }
}, "Emacs-like keys")

↑これは公式のサンプルですが、xkeysnailではこのような定義でキーマップをカスタマイズできます。ベタに修飾キー+何かのキーをマップしたり、ワンショットモディファイヤ(押して離すとXX、押しながらだとYYみたいな)もできますし、Emacsみたいに2ストロークにもできるし、特定のアプリケーションにのみ発動する定義もできたりと、かなり高機能な印象です。

やれることは公式サンプルを見たほうが早いです。

https://github.com/mooz/xkeysnail/blob/master/example/config.py

インストール環境

  • Ubuntu 20.04
  • xkeysnail 0.3.0

インストール方法

公式の手順通りインストールします。Ubuntu 20.04はpython3はデフォルトで入ってますがpipは入ってません。pipをインストールして、そのpipでxkeysnailをインストールします。

$ sudo apt install python3-pip
$ sudo pip3 install xkeysnail

設定

自分のした設定は次のような感じ。

◆config.py
import re
from xkeysnail.transform import *


define_modmap({
    Key.CAPSLOCK: Key.LEFT_CTRL,
    Key.RIGHT_CTRL: Key.LEFT_CTRL,
})


define_multipurpose_modmap({
    Key.Q: [Key.Q, Key.RIGHT_META],
    Key.APOSTROPHE: [Key.APOSTROPHE, Key.RIGHT_CTRL],
})


define_keymap(None, {
    # Vim-like
    K("RSuper-h"): K("Left"),
    K("RSuper-j"): K("Down"),
    K("RSuper-k"): K("Up"),
    K("RSuper-l"): K("Right"),
    # Emacs-like
    K("RC-a"): K("Home"),
    K("RC-e"): K("End"),
    K("RC-d"): K("Delete"),
    K("RC-h"): K("Backspace"),
    K("RC-k"): [K("Shift-End"), K("Delete")],
    # Mac-like
    K("Super-Backspace"): [K("Shift-Home"), K("Backspace")],
    # Eclipse-like
    K("Shift-Enter"): [K("End"), K("Shift-Enter")],
    K("C-Shift-Enter"): [K("Up"), K("End"), K("Enter")],
})

Vim風カーソル移動

すべてのアプリケーションにおいて、q + h/j/k/lでカーソル移動ができるようにしました。なんでqなの?となるかもしれませんが、通常の修飾キー(Ctrl/Alt/Shift/Window)を使用すると、他のショートカットとバッティングしたり、修飾キーを離した時にワンショットが発火してしまったりとトラブルになることが多いです。例えば、最初はqではなく左Winキー + h/j/k/lでカーソル移動できるようにしていたのですが、アプリケーションによっては左Winキーを離した時に発火してしまい、Gnomeのアクティビティ画面が表示されてしまうという不都合がありました。左Winではなく右Winにするとアクティビティ画面は起動しないのですが、キーボードによっては右Winキーが無かったりするので万能ではありませんでした。

というわけで凝ったことをするなら「修飾キーではない通常キーを修飾キーとして使う」方が筋がいいと思います。

で、どうしてqキーを選んだかというと、

  • 頻繁に使わない
  • 左手のホームポジションから小指が容易に届き、右手のhjklにも無理がない

という理由でqにしました。

上記config.pyの次の部分がqを修飾キーとしても使えるようにする設定にあたります。

define_multipurpose_modmap({
    Key.Q: [Key.Q, Key.RIGHT_META],
    ...
})

define_multipurpose_modmap という関数で複数目的の修飾キーを設定できます。ここでは『qを押して離すとqキー』『押しっ放しの間はRIGHT_META(右Winキー)』という設定にしています。押しっ放しのときに右Winというのがポイント。

そしてカーソル移動を実現している設定は次の部分にあたります。

define_keymap(None, {
    # Vim-like
    K("RSuper-h"): K("Left"),
    K("RSuper-j"): K("Down"),
    K("RSuper-k"): K("Up"),
    K("RSuper-l"): K("Right"),
    ...

RSuper というのが右Win(RIGHT_META)にあたります。なんでキーの指定の仕方が違うんだよって思うかもしれません。このあたりは公式のソースを見てみてください。

https://github.com/mooz/xkeysnail/blob/master/xkeysnail/key.py

Emacs風キーマップ

Emacsはちょっとしか触ってこなかったですが、結構便利だったなと思うキーマップがあったので取り入れてみました。こちらは'で発動するようにしています。このアポストロフィ(シングルクォート)はUSキーボードだと右手の小指ですぐに押せます。

define_multipurpose_modmap({
    ...
    Key.APOSTROPHE: [Key.APOSTROPHE, Key.RIGHT_CTRL],
})

押しっ放しにしたときは右Ctrlキーになります。

この右Ctrlを使ってEmacs風キーマップを設定します。

define_keymap(None, {
    ...
    # Emacs-like
    K("RC-a"): K("Home"),
    K("RC-e"): K("End"),
    K("RC-d"): K("Delete"),
    K("RC-h"): K("Backspace"),
    K("RC-k"): [K("Shift-End"), K("Delete")],
    ...

これらはMacならデフォルトでCtrlキーに割り当てられてます。結構色んなところで標準で取り入れられてるキーマップなので、Vimmerでも覚えておいたほうがいいかと思います。

なお、右Ctrlキーを割り当てちゃうと、普通の右Ctrl+a/e/d/h/kが使えなくなるんじゃね?と思うかもしれませんが、先の設定で右Ctrlは左Ctrlに変更しているので大丈夫です。

define_modmap({
    ...
    Key.RIGHT_CTRL: Key.LEFT_CTRL,
})

なんだかすごくややこしい話ですが、要するに

  1. 元の右Ctrlは左Ctrlにマップして退避
  2. ' を押しっぱなしの間は1でフリーになった右Ctrlにマップ

というふうになっています。ちょっと混乱する場合は、落ち着いてconfig.pyを上から見ていってくだされ。

Mac風キーマップ

Mac用のUSキーボードでLinuxを使っていると、「カーソル位置から行頭まで削除」するのに、ついついCMD+Backspaceを押してしまうんですよね・・・。このショートカットはかなり使用頻度が高いので、Linuxでも実現しておきます。

define_keymap(None, {
    ...
    # Mac-like
    K("Super-Backspace"): [K("Shift-Home"), K("Backspace")],
    ...

通常であればCMDキーがWinキーに割り当てられているはずです。SuperはWinキーなので、つまりCMD+Backspaceを押すと行頭まで削除してくれるようになります。ただ、行頭まで削除をShift+Home→Backspaceで実現しているだけなので、Vimのような特殊な(?)環境下では上手くいきませんが、そこはご愛嬌ということで。

なお、本気でカスタマイズしたいなら、アプリケーションごとに設定を施すこともできます。例えば公式の例には、Google ChromeとFirefoxにのみ有効な設定例が載っています。

define_keymap(re.compile("Firefox|Google-chrome"), {
    # Ctrl+Alt+j/k to switch next/previous tab
    K("C-M-j"): K("C-TAB"),
    K("C-M-k"): K("C-Shift-TAB"),
    ...

Eclipse風?キーマップ

eclipse固有のものではないですが、Shift+Enterでカーソル下に行を挿入、Ctrl+Shift+Enterでカーソル上に行を挿入、というものがあります。あれってプログラミングしてるとすごい便利だなあと感じることが多いです。というわけで、どんなエディタにも必要だと感じたので設定してみました。

define_keymap(None, {
    ...
    # Eclipse-like
    K("Shift-Enter"): [K("End"), K("Shift-Enter")],
    K("C-Shift-Enter"): [K("Up"), K("End"), K("Enter")],
})

ポイントは、Shift+Enter時の最後にEnterではなくShift+Enterを送っていることです。これは、エディタによっては元々Shift+Enterにショートカットが割り当てられていることがあるため、それを殺さないようにするためです。例えばWordpressでも、通常のEnterのみと、Shift+Enterでは違います。Wordとかもそうだったかな?そういったアプリ固有のキーマップを活かすようにしています。

自分の設定を試す

自分で作成したconfig.pyですが、xkeysnailを実行して実際に試してみます。

試すには次のようにxkeysnailをrootで実行して設定ファイルを指定します。

$ sudo xkeysnail config.py

たぶんエラーが出ると思います。セキュリティ上の理由でrootはroot以外のユーザーが起動したXサーバに接続することができませんのです。xhostコマンドでrootを一時的に追加します。

$ xhost +SI:localuser:root

もういちどxkeysnailを実行してみましょう。実行したターミナルはそのままにしておいて、他のアプリケーション上で設定したキーマップを試してください。うまく設定できました?

自動起動

うまく設定できたらsystemdに登録して自動起動するようにしておきます。xkeysnailはrootで実行する必要がありますが、どっちみち先程のxhostコマンドは一般ユーザーで実行する必要があるので、一般ユーザーで起動するようにしたほうがラクです。というわけで一般ユーザーに必要な権限だけ与えて、user環境のsystemdに登録することにします。

まずはグループを作成します。uinputというグループ名にします。

$ getent group uinput  # 一応すでに存在しないか見ておく
$ sudo groupadd uinput

できましたら、inputと先程作成したuinputグループにユーザーを追加します。

$ sudo usermod -aG input,uinput あなたのユーザー名
$ getent input   # 確認
$ getent uinput  # 確認

あなたのユーザーがinput, uinputグループに追加されていることを確認しました。

続いてudev ruleを作成します。作成場所は/etc/udev/rules.dの中です。

◆/etc/udev/rules.d/70-input.rules
KERNEL=="event*", NAME="input/%k", MODE="660", GROUP="input"
◆/etc/udev/rules.d/70-uinput.rules
KERNEL=="uinput", GROUP="uinput"

上記2ファイルを配置したら、一旦PCを再起動します。

続いて、以下のファイルを~/.config/systemd/userに作成します。ディレクトリがなかったら作ってね。あなたのユーザー名は適宜変更してください。

◆~/.config/systemd/user/xkeysnail.service
[Unit]
Description=xkeysnail

[Service]
KillMode=process
ExecStartPre=/usr/bin/xhost +SI:localuser:root
ExecStart=/usr/local/bin/xkeysnail /home/あなたのユーザー名/.config/xkeysnail/config.py
Type=simple
Restart=always
RestartSec=10s

# たぶん :0 で問題ないと思いますが環境にもよります。`echo $DISPLAY`の値を設定してください
Environment=DISPLAY=:0

[Install]
WantedBy=default.target

軽くポイントを説明しておくと、

  • ExecStartが実行コマンドです
  • ExecStartPreが実行コマンド前に実行するコマンドです(さっきのxhostコマンド)
  • 落ちたりしたら自動的にリスタートするようにしていますが、そのリトライはRestartSecで10秒後を指定してます

といった感じです。

では次にキーマップ設定であるconfig.pyを~/.config/xkeysnail/config.pyに配置してください。

ではサービスとして登録しましょう。

$ systemctl --user enable xkeysnail

登録できたら起動してみます。

$ systemctl --user start xkeysnail

ちゃんと起動しているかステータスも見ておきます。

$ systemctl --user status xkeysnail
● xkeysnail.service - xkeysnail
     Loaded: loaded (/home/あなたのユーザー名/.config/systemd/user/xkeysnail.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2020-04-25 14:24:57 JST; 3h 6min ago
    Process: 6409 ExecStartPre=/usr/bin/xhost +SI:localuser:root (code=exited, status=0/SUCCESS)
   Main PID: 6410 (xkeysnail)
     CGroup: /user.slice/user-1000.slice/user@1000.service/xkeysnail.service
             └─6410 /usr/bin/python3 /usr/local/bin/xkeysnail /home/あなたのユーザー名/.config/xkeysnail/config.py

Active: active (running) になっていれば成功です。設定したキーマップを試してみましょう。

というわけで快適なLinux生活になりました。作者には強いありがたみ感じます。

関連する記事


コメントする

メールアドレスが公開されることはありません。

CAPTCHA


このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください