mythfinder

⌨️

XKBでJIS配列のキーボードをUS配列 / HHKB風にする

結論からいうと、JIS配列はちょっとした手続きでHHKB風味にマップできる。

背景

あまり言及されないが、キーボード設定の自在さは Linuxデスクトップの強みのひとつだ。Linuxの表示系には(X / Waylandどちらであれ)XKBというキーボードユーティリティが組み込まれており、これで柔軟かつ包括的なキーボード設定ができる。一方、ほかのOS ── たとえばWindows ── では数多のツールが開発されてきたとはいえ、どれも非公式であって決定的とはいえなかった1。このため、Win(Super)キーが OS側の謎のショートカットどもに支配されていて上書きできない、というようなアホらしいことが起こる。Linuxではそういうことはない。端的に:キーの生殺与奪を握られずに済む

このことは組み込みのキーボードとうまく付き合っていくほかないラップトップなんかでは特に有効で、今回の主題もここにある。つまり、気に入らない配列と出会ったらとにかく再マップしてやればよいのである。

要件

JIS配列のThinkpadを対象に、以下の要件を満たすよう設定した。

1. HHKB風キーマップ・キーバインド

US配列に準拠。

2. US配列

私は US配列主義者ゆえ、JIS配列をUS配列として強引に利用する設定も行う(特にEnter周辺)。

3. 非ラテン文字の入力

さらに、趣味の都合上ロシア語(キリル文字)やセルビア・クロアチア語(ラテン文字拡張、キリル文字拡張)も入力するため、US配列とこれらの言語の配列をスムーズに切り替える必要があった2オタクってクソめんどくさいね!

解決策

最終的な変更の一覧

keyremap / role
CapsLockCtrl
左右CtrlAlt
Deleteバッククォート
かな/カナSuper
無変換Mod
半角/全角Esc
Super + Space言語配列切り替え
Mod + misc色々

ディレクトリ構成

~/.xkb
├── compat
│   └── mycompat
├── keymap
│   └── hhkb
├── symbols
│   ├── mysymbol1
│   └── mysymbol2
└── types
    └── mytypes

設定ファイルとその説明

全体を包括する設定はkeymap/hhkbにある。

keymap/hhkb
xkb_keymap {
    xkb_keycodes  { include "evdev+aliases(qwerty)" };
    xkb_types     { include "complete+mytypes(hhkb)" };
    xkb_compat    { include "complete+mycompat(hhkb)" };
    xkb_symbols   { include "pc+us+ru:2+hr:3+inet(evdev)+group(win_space_toggle)+ctrl(nocaps)+jp(hztg_escape)+ctrl(rctrl_ralt)+ctrl(swap_lalt_lctl)+mysymbol1(hhkb)+mysymbol2(mhkn)" };
    xkb_geometry  { include "pc(pc105)" };
};

今回影響するのはxkb_typesxkb_compatxkb_symbolsの行。上のディレクトリ構造と照らし合わせるとなんとなく対応がわかると思うが、以降のファイルの内容をこれらの行で読み込んでいる。各ファイルの詳細に移る前に、xkb_symbolsの行で利用している、XKBに予め用意されたプリセットについて補足しておく。

項目備考
us+ru:2+hr:3利用したい言語配列を略号で記載する。n 個目(n>=2)の言語は+<lang>:nのように記載するのがミソ3
group(win_space_toggle)上で記載した配列間をsuper + spaceキーで切り替える。
ctrl(nocaps)CapsLockCtrl
jp(hztg_escape)不要な半角/全角キーをEscにマップ。
ctrl(rctrl_ralt), ctrl(swap_lalt_lctl)左右それぞれのAltCtrlを入れ替え

おわかりかと思うが、実はこのプリセットの組み合わせだけで要件のうち後者二つ(US配列、多言語配列対応)と前者の一部は実現できている。したがって以降ではModキーの挙動を設定していく。

compat

まずcompat。 無変換Mod5(そういう修飾キー)に割り当てている。

compat/mycompat
xkb_compatibility "hhkb" {
  interpret Muhenkan+AnyOfOrNone(all) {
      action = SetMods(modifiers=Mod5);
  };
};

types

次にtypes。ここではMUHENKANというtypeを定義して、Shiftや上で定めたMod5を押した時にどのLevelの挙動をさせるかを指定する。

types/mytypes
xkb_types "hhkb" {
  type "MUHENKAN" {
    modifiers = Shift+Mod5;
    map[Shift] = Level2;
    map[Mod5] = Level3;
    map[Shift+Mod5] = Level3;
    level_name[Level1] = "Base";
    level_name[Level2] = "Shift";
    level_name[Level3] = "Muhenkan";
  };
};

symbols

最後にsymbols。二つに分けているが特に意味はない。

下手な説明よりファイルを見たほうがよほどわかりやすい。Group1, 2, 3は先程指定した三つの言語(us, ru, hr)に、[]の中の並びは同じくtypesで定義した(あるいははじめから決まっている)各Levelにそれぞれ対応している。

symbols/mysymbol1
xkb_symbols "hhkb" {
    # Hiragana-Katakana => Super
    key <HKTG> { [ Super_R ] };
    key <HENK> { [ Super_R ] };

    # JIS => US
    key <AE13> { [ backslash, bar ] };
    key <DELE> { [ grave, asciitilde ] };
    key <AC12> { [ Return ] };
    # modifier_map Super { <HKTG>, <HENK> }
};
symbols/mysymbol2
xkb_symbols "mhkn" {
  key <AD07> {
      type[group1]= "MUHENKAN",
      type[group2]= "MUHENKAN",
      type[group3]= "FOUR_LEVEL_SEMIALPHABETIC",
      symbols[Group1]= [               u,               U,           Prior ],
      symbols[Group2]= [    Cyrillic_ghe,    Cyrillic_GHE,           Prior ],
      symbols[Group3]= [               u,               U,       downarrow,         uparrow ]
  };
  key <AD08> {
      type[group1]= "MUHENKAN",
      type[group2]= "MUHENKAN",
      type[group3]= "FOUR_LEVEL_SEMIALPHABETIC",
      symbols[Group1]= [               i,               I,              Up ],
      symbols[Group2]= [    Cyrillic_sha,    Cyrillic_SHA,              Up ],
      symbols[Group3]= [               i,               I,      rightarrow,        idotless ]
  };
  key <AD09> {
      type[group1]= "MUHENKAN",
      type[group2]= "MUHENKAN",
      type[group3]= "FOUR_LEVEL_ALPHABETIC",
      symbols[Group1]= [               o,               O,            Next ],
      symbols[Group2]= [  Cyrillic_shcha,  Cyrillic_SHCHA,            Next ],
      symbols[Group3]= [               o,               O,          oslash,          Oslash ]
  };
  key <AC07> {
      type[group1]= "MUHENKAN",
      type[group2]= "MUHENKAN",
      type[group3]= "FOUR_LEVEL_SEMIALPHABETIC",
      symbols[Group1]= [               j,               J,            Left ],
      symbols[Group2]= [      Cyrillic_o,      Cyrillic_O,            Left ],
      symbols[Group3]= [               j,               J,       dead_hook,       dead_horn ]
  };
  key <AC08> {
      type[group1]= "MUHENKAN",
      type[group2]= "MUHENKAN",
      type[group3]= "FOUR_LEVEL_SEMIALPHABETIC",
      symbols[Group1]= [               k,               K,            Down ],
      symbols[Group2]= [     Cyrillic_el,     Cyrillic_EL,            Down ],
      symbols[Group3]= [               k,               K,         lstroke,       ampersand ]
  };
  key <AC09> {
      type[group1]= "MUHENKAN",
      type[group2]= "MUHENKAN",
      type[group3]= "FOUR_LEVEL_ALPHABETIC",
      symbols[Group1]= [               l,               L,           Right ],
      symbols[Group2]= [     Cyrillic_de,     Cyrillic_DE,           Right ],
      symbols[Group3]= [               l,               L,         lstroke,         Lstroke ]
  };
  key <AB07> {
      type[group1]= "MUHENKAN",
      type[group2]= "MUHENKAN",
      type[group3]= "FOUR_LEVEL_SEMIALPHABETIC",
      symbols[Group1]= [               m,               M,            Home ],
      symbols[Group2]= [ Cyrillic_softsign, Cyrillic_SOFTSIGN,        Home ],
      symbols[Group3]= [               m,               M,         section,       masculine ]
  };
  key <AB08> {
      type[group1]= "MUHENKAN",
      type[group2]= "MUHENKAN",
      type[group3]= "FOUR_LEVEL",
      symbols[Group1]= [           comma,            less,            Menu ],
      symbols[Group2]= [     Cyrillic_be,     Cyrillic_BE,            Menu ],
      symbols[Group3]= [           comma,       semicolon,            less,        multiply ]
  };
  key <AB09> {
      type[group1]= "MUHENKAN",
      type[group2]= "MUHENKAN",
      type[group3]= "FOUR_LEVEL",
      symbols[Group1]= [          period,         greater,             End ],
      symbols[Group2]= [     Cyrillic_yu,     Cyrillic_YU,             End ],
      symbols[Group3]= [          period,           colon,         greater,        division ]
  };
};

設定の適用

以上のファイル群を~/.config/xkb/とか~/.xkb/とか適当な場所に配置して、各自の環境ごとに読み込めば終了。例えばswayなら:

~/.config/sway/config
type:keyboard {
  xkb_file ~/.xkb/keymap/hhkb
}

おわりに

いかがでしたか? これが HHKB に脳を破壊された哀れなオタクの黒魔術である

書いたあとで気づいたが、みんな大好きArchWikiにもわりと詳細な記述があるので、そちらを参照したほうが良いかもしれない。

この設定ファイルがどのように編み出されたかに関する付記

この設定をひねり出すために、XKBのドキュメントを丁寧に紐解いた……などということはなく、xkbcomp $DISPLAY output.xkbが吐いた設定ダンプとウンウン向き合う涙ぐましい努力があったことを、ここに申し添えておく。

参考サイト

Ubuntu:「無変換+○」にカーソル移動系ホットキーを設定する(xkb編) Ubuntu 14.04 で xkb の設定を利用して、「無変換+○○」系のホットキーを設定しました。設定方法と、わかったこと、わかっていないこと、できなかったことなどを書いておきます。 設定し... did2memo.net
Ubuntu:「無変換+○」にカーソル移動系ホットキーを設定する(xkb編)

変換・無変換キーのFnキー化について(Ubuntu) | OJAのメモ帳 Windowsでは「AutoHotkey」を使ってキーバインドの変更をしていたが(過去の記事"AutoHotkeyの設定について"参照)、Ubuntuでも同じように変換・無変換キーをFn化するときのメモ。 ojamemo.com

脚注

  1. 最近はMicrosoft謹製PowerToysの登場で風向きが変わりつつあるが

  2. これを書いている途中、XKBより上位のfcitx5のレイヤでも多言語切り替えを設定できることに気づいたが、IMEなしで入力できる言語をIMEの層に持ち込むのはあまりエレガントでないので(という言い訳をつけて)却下。

  3. これを発見するのにだいぶ苦労した