XKB入門 UbuntuでCtrl-hをBackSpaceにする

OS上の全アプリに対して Ctrl-h を BackSpace として使えるようにする際に、
XKBの設定をいじくり回したのですが、理解に苦しむところが多かったため、まとめます。
(注)理解しきれていないところが多くあります。誤りがあれば遠慮なくコメントください。

確認環境

  • Lubuntu 16.04

モチベーション

Lubuntuにて、Terminal上ではCtrl-h(Ctrlキーとhキーの同時押し)がBackSpaceとして機能するが、WebブラウザなどGUIアプリ上では機能しない。(UbuntuだとGnomeTweakで簡単にできるのだが、Lubuntuだとできず)
→ XKBというやつで設定できるらしいゾ(xmodmapは古いらしいゾ)
→ 他にも色々できそうだし、使ってみるゾ

XKBとは

  • 正式名称:X Keyboard Extension
  • X Window Systemの一部で、キーボードの修飾キー(ShiftやCtrl)との組み合わせ動作、キーグループによる動作切り替えなどを柔軟に設定できる。

用語/事前知識

modifier key

ShiftやCtrlなど、単体で押下しても効果がないが、他のキーと組み合わせて使うキーのこと。AltGrなど日本人には馴染みがないものもある。qualifier keyも同義。

ISO 9995

キーボードレイアウトに関する標準。XKBの設定ファイルに唐突に ISO_*** と出てくるので困惑する。
ISO/IEC 9995

Key Group

ローマ字入力している日本人には必要ない概念なので分かりづらい。要はKey Groupが異なるということはアルファベットが異なるという理解で良さげ。(ロシアや北欧など、英語とは異なるアルファベットの国のキーボードを考えるとKey Groupを切り替える必要性がわかる)

Level

1のキーを単体で押すと”1”が表示される、これがLevel 1.
1のキーをShiftと一緒に押すと”!”が表示される、これがLevel 2.
LevelとGroupの関係性はこのページが分かりやすい。

Level 3はAltGr(or 右Alt)と、Level 4はAltGr/右Alt + Shiftと紐付いていることが一般的な模様。Wikipediaを参照。


手順

一応、現在のXKBの設定を把握する

本記事には直接的には関係はないが、XKBの構造を理解しておく上で確認しておくことをオススメ。

1
2
3
4
5
6
7
8
~$ setxkbmap -print
xkb_keymap {
xkb_keycodes { include "evdev+aliases(qwerty)" };
xkb_types { include "complete" };
xkb_compat { include "complete" };
xkb_symbols { include "pc+us+inet(evdev)+ctrl(nocaps)" };
xkb_geometry { include "pc(pc105)" };
};

-verbose 10オプションでより詳細な情報が出ます。
上記内容からXKBがどの設定ファイルが読み込んでいるかが分かります。
symbolsであれば、pc -> us -> inetのevdevセクション -> ctrlのnocapsセクションの順で読み込まれます。
pcやinet等は全てファイル名で、/usr/share/X11/xkbにあります。(OSによっては違うかも)

1
2
3
4
5
6
7
8
9
~$ cd /usr/share/X11/xkb/
xkb$ tree -L 1
.
├── compat
├── geometry
├── keycodes
├── rules
├── symbols
└── types

各ディレクトリの中に setxkbmapの結果で表示された設定ファイルが存在します。

現在の最終的な設定

上記の設定ファイルを読み込んだ結果、現在最終的にどのような設定になっているかが以下のコマンドで見れます。

1
2
3
4
5
6
7
8
9
10
11
12
~$ xkbcomp $DISPLAY current.xkb
~$ head current.xkb
xkb_keymap {
xkb_keycodes "evdev_aliases(qwerty)" {
minimum = 8;
maximum = 255;
<ESC> = 9;
<AE01> = 10;
<AE02> = 11;
<AE03> = 12;
<AE04> = 13;
<AE05> = 14;

current.xkbは通常1000行以上あると思いますが、大まかに5つのセクションから構成されます。

xkb_keycodes

上記のheadコマンドでチラ見えしていますが、<AE01> = 10 のような内容です。
左辺がXKB内部で使われるシンボリックなkeyname, 右辺の10は低レイヤから受信したkeycodeです。
このレイヤを挿れておくことで、以降の処理を物理的なキーボード種別に対する依存性から分離していると考えれば良いと思います。

xkb_types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
xkb_types "complete" {
virtual_modifiers NumLock,Alt,LevelThree,LAlt,RAlt,RControl,LControl,ScrollLock,LevelFive,AltGr,Meta,Super,Hyper;
type "ONE_LEVEL" {
modifiers= none;
level_name[Level1]= "Any";
};
type "TWO_LEVEL" {
modifiers= Shift;
map[Shift]= Level2;
level_name[Level1]= "Base";
level_name[Level2]= "Shift";
};
type "ALPHABETIC" {
modifiers= Shift+Lock;
map[Shift]= Level2;
map[Lock]= Level2;
level_name[Level1]= "Base";
level_name[Level2]= "Caps";
};

(これ以降もたくさんあります。最初の方のみ表示しています)
このtypesにおいて、modifierによってlevelをどう変化させるかを定義します。
例えばALPHABETICタイプでは、ShiftおよびLock(CapsLock)で修飾した場合はLevel2(要は大文字)にするとしています。
level_nameというのは只の名前付けなので実動作上は何でもよい模様。

xkb_compatibility

正直ここはあまり理解していません。ただ、typesセクションで頻出するvirtual modifierが概ねここで定義されていることを知っておくとよさそうです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
xkb_compatibility "complete" {

virtual_modifiers NumLock,Alt,LevelThree,LAlt,RAlt,RControl,LControl,ScrollLock,LevelFive,AltGr,Meta,Super,Hyper;

interpret.useModMapMods= AnyLevel;
interpret.repeat= False;
interpret.locking= False;
interpret ISO_Level2_Latch+Exactly(Shift) {
useModMapMods=level1;
action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
};
interpret Shift_Lock+AnyOf(Shift+Lock) {
action= LockMods(modifiers=Shift);
};
interpret Num_Lock+AnyOf(all) {
virtualModifier= NumLock;
action= LockMods(modifiers=NumLock);
};
interpret ISO_Level3_Shift+AnyOf(all) {
virtualModifier= LevelThree;
useModMapMods=level1;
action= SetMods(modifiers=LevelThree,clearLocks);
};

real modifierの定義は直感的ですが、virtual modifierの定義は文法的に直感に反していて分かりづらいです。

1
2
3
4
// definition of real modifier: R
modifier_map R { <keysym> };
// definition of virtual modifier: V
interpret <keysym> { virtualMod = V; };

ここで試しに上に出てきたISO_Level3_Shiftというkeysymを追ってみます(私は名前の意味不明さもあって混乱しました)。

1
2
3
4
5
6
7
8
9
10
11
12
// keycodes section
<LVL3> = 92;

// symbol section
key <LVL3> { [ ISO_Level3_Shift ] };

// compat section
interpret ISO_Level3_Shift+AnyOf(all) {
virtualModifier= LevelThree;
useModMapMods=level1;
action= SetMods(modifiers=LevelThree,clearLocks);
};

というわけでkeycode 92のキーがLevelThreeという名前のvirtual modifierとして使われるようです。
ちなみに私の日本語キーボードにはkeycode 92のキーはありません。(keycodeの調べ方は後述)

xkb_symbols

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xkb_symbols "pc_us_inet(evdev)_ctrl(nocaps)" {
name[group1]="English (US)";
key <ESC> { [ Escape ] };
key <AE01> { [ 1, exclam ] };
key <AE02> { [ 2, at ] };
key <AE03> { [ 3, numbersign ] };
key <AE04> { [ 4, dollar ] };
key <AE05> { [ 5, percent ] };
key <AE06> { [ 6, asciicircum ] };
key <AE07> { [ 7, ampersand ] };
key <AE08> { [ 8, asterisk ] };
key <AE09> { [ 9, parenleft ] };
key <AE10> { [ 0, parenright ] };
key <AE11> { [ minus, underscore ] };
key <AE12> { [ equal, plus ] };
key <BKSP> { [ BackSpace, BackSpace ] };
key <TAB> { [ Tab, ISO_Left_Tab ] };
key <AD01> {
type= "ALPHABETIC",
symbols[Group1]= [ q, Q ]
};

このセクションが最終的な出力に直接影響する箇所です。
<AE01>というkeynameに対する右辺は[1, exclam]となっていますが、[ Level 1の出力, Level 2の出力]を意味します。
つまり1のキーを押下した時、Level 1として”1”、Level 2として”!”(exclam)を出力することになります。
Level 4まで存在するtypeが指定されている場合は [Level 1の出力, Level 2の出力, Level 3の出力, Level 4の出力]と記載します。

xkb_geometry

物理的なキー配置を定義するファイルですが、ソフトウェア動作上はあまり気にしないで良さそうです。


Ctrl-hをBackSpaceにする

さて、上記を踏まえてhキーをCtrlキーでmodifyした時に出力するkeysymをBackSpaceに変更します。
current.xkbにおけるhキーの挙動を定義している箇所を特定する必要がありますが、xevコマンドでkeycodeから辿って特定できます。

1
2
3
4
5
KeyRelease event, serial 48, synthetic NO, window 0xe00001,
root 0x16e, subw 0x0, time 4408800, (1404,606), root:(1405,656),
state 0x0, keycode 43 (keysym 0x68, h), same_screen YES,
XLookupString gives 1 bytes: (68) "h"
XFilterEvent returns: False

xevコマンドを実行したあとにhキーを押下すると上記のような結果が表示されます。keycodeは43とわかりました。
current.xkbの中で43を探します。

1
2
// keycode section
<AC06> = 43;

よってhキーに対応するkeynameは<AC06>です。今度はsymbolsセクションにおける<AC06>を探します。

1
2
3
4
5
// symbols section
key <AC06> {
type= "ALPHABETIC",
symbols[Group1]= [ h, H ]
};

現状はLevel 1に小文字、Level 2に大文字が割り当てられています。
Ctrlキーでmodifyした時にLevel 3の挙動としてBackSpaceを出力するようにしたいです。
シンプルなtypeを自分で定義してみます(もっと適切なやり方がありそうです。ここで記載する内容の場合、元々ALPHABETICで定義しているCapsLockによるLevel 2出力は消えますが、個人的にCapsLockは使わないため、気にしていません)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type "ALPHABETIC" {
modifiers= Shift+Lock;
map[Shift]= Level2;
map[Lock]= Level2;
level_name[Level1]= "Base";
level_name[Level2]= "Caps";
};

// newly added
type "PC_CONTROL_LEVEL3" {
modifiers= Shift+Control;
map[Shift]= Level2;
map[Control]= Level3;
level_name[Level1]= "Base";
level_name[Level2]= "Shift";
level_name[Level3]= "Control";
};

合わせてsymbols sectionも書き換えます。

1
2
3
4
key <AC06> {
type= "PC_CONTROL_LEVEL3",
symbols[Group1]= [h, H, BackSpace]
};

設定を反映する

変更した.xkbファイルをnew.xkb等として適当に保存しておき、以下で設定を反映します。

1
xkbcomp new.xkb $DISPLAY

変更は即時反映されます(再起動/再ログイン不要)。
設定反映するやり方は色々あるようなのですが、現状の私の環境の場合、起動プロセス中に何か(Fcitx? GnomeTweak?)がXKBの設定を弄っているようなので
起動完了後に上記コマンドが実行されるようにしています。
このあたりはあまり調べられていません。


参考リンク

以上、ご参考になれば幸いです。