LoginSignup
5
9

More than 3 years have passed since last update.

Ubuntu で Mac / emacs キーバインディング(キーボードショートカット)を使いたい xkeysnail のススメ

Last updated at Posted at 2020-09-25

キーボード・ショートカットを変換できる xkeysnail は素晴らしいので使おうという話.

Mac では Ctrl+a で行頭に移動、Ctrl+k で行末までカットといったことが emacs・ターミナルだけではなくあらゆる所で出来る.これを Ubuntu でも実現したい.Gnome で emacs キーバインディングを実現する gnome-tweak-tool key-theme=Emacs では全然満足できなかった; ブラウザ内で Windows ライクなショートカットで上書きされてしまい、特に Jupyter Notebook で Ctrl-a が「全体を選択」になってしまうのがきつい.xkeysnail では Ctrl+a を Home キーだと思わせることで行頭への移動を実現している、このためブラウザの中でも(ウェブアプリが普通の windows式に対応している限り)使える.Ubuntu 要素は特になく Linux 全般で使える.

1. インストール

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

pip は Python のライブラリパッケージ管理ソフト.

2. config.py を作る

とりあえずはどこでも良いので config.py を作る.公式 config.py はこちら

3. 実行

その config.py のあるディレクトリで:

$ xhost +SI:localuser:root
$ sudo xkeysnail config.py

Troubleshooting

1. command not found: xkeysnail

sudo pip3 install ... ではなく pip3 install ... とやると ~/.local/bin/xkeysnail にインストールされ、PATH がたぶん通ってない.

$ pip3 show xkeysnail
(中略)
Location: /usr/local/lib/python3.6/dist-packages

を見るとインストール先がわかる. conda やら pyenv などで多くの環境をつかっているならどの環境にインストールされたか注意.sudo を使った場合と使わなかった場合で環境が異なるかもしれない.sudo pip をつけわすれたのをやり直すのは

$ pip3 uninstall xkeysnail
$ sudo pip3 install xkeysnail

あるいはお好みで PATH を通す、絶対PATHで実行するなど.

2. ImportError: sys.meta_path is None, Python is likely shutting down

もっと上を見てみるともっと具体的なエラーがみつかる.例えば

FileNotFoundError: [Errno 2] No such file or directory: 'config.pu'

は config.py のファイル名を間違えた.config.py の中でキーの名前を間違えているかもしれない.

3. Xlib.error.DisplayConnectionError: Can't connect to display ":0":

xhost + をやっていないと出る。

4. config.py を編集する

  • キーボードは Apple Magic Keyboard. Emacs 以外では ⌘C で copy, ⌘S で save など command ⌘ を Windows の ctrl のように使いたい.
  • しかし、⌘ space は 英語日本語の言語切り替えに使い、emacs の mark set ctrl-space とは別に使いたい。つまり command=ctrl にしてしまうのはやりすぎ.
  • Ubuntu には左⌘ (左 windows key) がすべてのウインドウを小さめに表示する機能に割りて当てられている. なので、左⌘で右⌘を使ってることにして無効化する。
  • Caps lock は使わないので他のキーに割り当てて有効に使う。
  • Ctrl+a, Ctrl-e, Ctrl-k などの emacs keybindings は公式 config.py のものを使う。これが一番大事。

# -*- coding: utf-8 -*-
import string
from xkeysnail.transform import *

# define timeout for multipurpose_modmap
define_timeout(1)

# [Global modemap] Change modifier keys as in xmodmap
define_modmap({
    Key.CAPSLOCK: Key.ESC,  # caps lock を Esc として使う。Ctrl派が多いかも
    Key.LEFT_META: Key.RIGHT_META, # overview 無効化: 左⌘ → 右⌘ 
})

# Always paste with ⌘-v including terminals
# None は無条件=すべてのアプリケーションを意味する
define_keymap(None, {
    K("Super-v"): [K("C-v"), set_mark(False)],
}, "Paste")


# Mac-like keybindings outside emacs and terminals
# 最低限の emacs keybinding.
# 公式 config.py はもっと emacs 化している. C-x C-s → 保存なんかもできる。
mapping = {
    # Beginning/End of line
    K("C-a"): with_mark(K("home")),
    K("C-e"): with_mark(K("end")),

    # Delete
    K("C-d"): [K("delete"), set_mark(False)],
    K("C-h"): with_mark(K("backspace")),

    # Kill line
    K("C-k"): [K("Shift-end"), K("C-x"), set_mark(False)],
}

# すべてのアルファベット α について ⌘α を Ctrl+α を押していることにする
for c in string.ascii_lowercase:
    mapping[K('Super-' + c)] = K('C-' + c)

# Emacsとターミナル以外でショートカットを変更する.Hyper は私の使っているターミナルアプリケーション名.
define_keymap(lambda wm_class: wm_class not in ("Emacs", "Hyper", "Gnome-terminal"),
              mapping, "Mac-like")

wm_class がアプリケーション名で xkeysnail を実行中のターミナルの標準出力をみればわかる。xkeysnail -q オプションをつけるとこの WM_CLASS やキーの表示は抑制される.

最低限の Python

Python に馴染みのない人向け.

コメント

# から行末までコメント.

文字列

  • "文字列", '文字列' どちらでもOK

辞書 dict

Key - Value ペアを保存するデータ構造; 定義したいショートカット: 実行されるショートカット を与えるのに使われている.

d = {key1: value1.
     key2: value2,
     key3: value3}

最後にカンマ (trailing comma) をつけても大丈夫、なくても良い {key3: value3,}.
```

作成後も追加できる:

d[key4] = value4

Tuple, list, set

wm_class not in ("Hyper", "Gnome-terminal", "Emacs")

文字列 wm_class が3つのうちどれでもない場合に True になる.否定演算子 not による

not (wm_class in ("Hyper", "Gnome-terminal", "Emacs"))

と同じ.どれかなら True にしたいなら

wm_class in ("Hyper", "Gnome-terminal", "Emacs")

("a", "b", "c") は変更できない配列 tuple. 細かいことを気にしなければ、変更できる配列 list ["a", "b", "c"] も 集合 set {"a", "b", "c"} も同じ.

ひとつなら、

wm_class == "Hyper"  # 等しい場合に True
wm_class != "Hyper"  # 等しくない場合に True

でもいい.要素ひとつの tuple を使う場合は最後のカンマを忘れずに:

wm_class in ("Hyper",)

カンマ無しだと

wm_class in "Hyper"

と同じになって意味が変わり、「wm_class は "Hyper" の部分文字列ですか?」という意味になる.

"ype" in "Hyper"  # => True
"ype" in ("Hyper")  # => True, ("Hyper") = "Hyper"
"ype" in ("Hyper", )  # => False, "ype" は "Hyper" 
                      # という要素ひとつからなる集合の要素ではない.

無名関数 lambda

f = lambda x: x**2

def f(x):
    return x**2

と同じ.アプリケーション名 wm_class に対してキーボード・ショートカットを変更するか否かを返す関数を定義するのに使われている.

define_keymap 関数

アプリケーション名 wm_class によってショートカットを変換するかどうかを変えられる.

def define_keymap(condition, mappings: dict, name: str):

条件 condition を満たした場合に 辞書 mappings で定義されたショートカット変換を行う.name は任意の設定の名前.

condition は3種類の指定の仕方がある:

condition:
  None: 無条件で適用
  関数: アプリケーション名 wm_class をとって True/False を返す関数
  正規表現: wm_class がマッチすべき正規表現(詳細略)

Key

Key.CAPSLOCK とか Key.ESC とかの一覧は key.py に書いてある.

f Key
ESC
TAB
CAPSLOCK
LEFT_CTRL, RIGHT_CTRL
LEFT_SHIFT, RIGHT_SHIFT
Key Apple Magic keyboard Windows keyboard
LEFT_META, RIGHT_META command ⌘ Win key
LEFT_ALT, RIGHT_ALT option ⌥ Alt

K 関数

K("C-b") で Ctrl+b を表す.C は Ctrl でもよく、LR をつけることで左か右に限定することもできる. キーを押してみて xkeysnail の標準出力をみればどう書くべきかわかる. Modifier key にはさらに左右どちらでもいい場合の総称や別名(Win は Super でもいいなど)がある.

Modifier key str for K
Ctrl ^ C, Ctrl, LC, RC, LCtrl, RCtrl
Shift Shift, LShift, RShift
Win/command ⌘ Win, Super, LWin, RWin, LSuper, RSuper
Alt/option ⌥ Alt, M, LAlt, RAlt, RM, LM

参考: create_modifiers_from_strings(modifier_strs) in transform.py.

5. 自動起動

実行スクリプトを書く

設定が完了しうまく動いているようならコンピュータ起動時に自動的に実行したい。service として登録する方法と .config/autostart を使う方法があるみたい。ここでは後者でいく。起動スクリプトは

$ mkdir ~/.xkeysnail

に入れることにする。必要なのは起動スクリプトだけだけどここに倣って start,stop,restart を作っておく。

$HOME/
  .xkeysnail/
    config.py
    start.sh
    stop.sh
    restart.sh
  .config/
    autostart/
      xkeysnail.desktop

start.sh

#!/usr/bin/env bash
xhost +SI:localuser:root
sudo xkeysnail -q $HOME/.xkeysnail/config.py &

stop.sh

#!/usr/bin/env bash

PID=`ps --no-heading -C xkeysnail -o pid | tr -d ' '`

if [ -n "$PID" ]; then
  sudo -u xkeysnail kill $PID
  echo "Stopped xkeysnail to kill $PID"
fi

restart.sh

#!/usr/bin/env bash

cd `dirname $0`
bash stop.sh
bash start.sh

start.sh に実行権限をつける:

$ chmod u+x start.sh

stop.sh, start.sh もお好みに応じて。あるいは

$ bash stop.sh
$ bash restart.sh

パスワードなしで sudo できるようにする

xkeysnail は sudo を必要とするけど自動起動時にパスワードを入力することはできない。
なので xkeysnail だけは sudo 無しで実行できるように /etc/sudoer を編集する; 普通のテキストファイルだけど編集には visudo を使えとある。

$ sudo visudo
username ALL=(ALL) NOPASSWD: /usr/local/bin/xkeysnail

の一行を追加する。username はあなたのユーザー名。 xkeysnail のフルパスがわからなければ、

$ which xkeysnail
/usr/local/bin/xkeysnail

自動起動ファイルを書く

~/.config/autostart/xkeysnail.desktop というテキストファイルを作る。

[Desktop Entry]
Type=Application
Version=0.3.0
Name=xkeysnail
GenericName=Keymapper
Exec=/home/username/.xkeysnail/start.sh
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true

ここでも username はあなたのユーザー名、/home/username/.xkeysnail/start.sh は start.sh をおいた場所。Ubuntu の GUI から Startup Applications Preferences をみつけて起動すると、xkeysnail が新たに加わっているはず。

設定完了。以降、起動すると自動的に xkeysnail が有効になっているはず。

Reference

作者の記事: https://qiita.com/mooz@github/items/c5f25f27847333dd0b37
GitHub: https://github.com/mooz/xkeysnail
偉大な先人達:
https://hidakatsuya.hateblo.jp/entry/2019/11/20/215608 and references therein.

5
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
9