Hardware Keyboard Remapper(OS に依存しないキーボードのキー入れ替え)を作った

Page content

皆さんはキーボードのキーを入れ替えてますか? キー入れ替えのメジャーな用途は、 Ctrl キーと Cap Lock キーの入れ替えでしょう。

そのような人は、 「OS のキー入れ替え設定」するのが新しい PC セットアップ時の手順の一つに なっている人も少なくないでしょう。

しかし、「OS のキー入れ替え設定」が常に出来るとは限りません。 例えば、共有 PC を使うケースや、 そもそも OS がキー入れ替えをサポートしていないケースなど。

そのような時に使うことを想定して作ったのが、Hardware Keyboard Remapper です。

Hardware Keyboard Remapper とは

今回作成した Hardware Keyboard Remapper は、 接続されているキーボードのキー入力を、 別のキー入力に変換して出力するプログラムで、 Raspberry pi zero w (以降 pi0w と略記)上で動作します。

構成

Hardware Keyboard Remapper は、以下の構成になります。

Bluetooth keyboard ===(Bluetooth)==> Raspberry pi zero w ===(USB)==> PC

pi0w と Bluetooth keyboard を予めペアリングしておき、 その pi0w を HID Keyboard として を PC に接続します。 そして、 BT keyboard のキー入力を pi0w 内で 任意の HID keyboard コードに変換して PC に通知することで、 OS に依存しないキーの置き換えを実現しています。

なお、半導体不足の影響か、現在 pi0w の入手性が著しく悪くなっています。 そのうち raspberry pi zero 2 w が発売されるとは思いますが、 それも日本で入手できるのは暫く先になりそうです。

しかし、ソフトウェアエンジニアなら、 使っていない pi0w の 1 台や 2 台程度、家に転がっていると思うので、 問題ないでしょう。

ちなみに、 M5stack を使って同じようなものを作ろうと取り組んでいる最中ではあります。 ただし、そちらは PC へのキー入力が USB ではなく、 BT になりそうです。

使用方法

残念ながら「アプリを実行すれば使える」という程お手軽なモノではありません。

ここでは、raspberry pi をセットアップした経験があることを前提に、 使用方法をします。

まず、簡単に手順をまとめると以下になります。

  • pi0w と BT keyboard をペアリング
  • pi0w の USB Gadget を有効化
  • github から hw-keyboard-remapper を clone
  • pi0w の USB Gadget の HID キーボードを登録
  • Key をカスタマイズ

pi0w と BT keyboard をペアリング

ペアリングは次の手順で行ないます。

  • BT keyboard をペアリング開始状態にする
  • 以下を pi0w で実行
$ sudo bluetoothctl
[bluetooth]# default-agent
[bluetooth]# scan on
  • 暫くすると、キーボードが検出され以下のような出力がされる
[NEW] Device XX:XX:XX:XX:XX:XX hogehogeKeyboard
  • 目的のキーボードであることを確認し、 この XX:XX:XX:XX:XX:XX の情報をもとに以下を実行する
[bluetooth]# pair XX:XX:XX:XX:XX:XX
[bluetooth]# trust XX:XX:XX:XX:XX:XX
[bluetooth]# connect XX:XX:XX:XX:XX:XX
[bluetooth]# exit

以上の設定を行なっておけば、次回からは自動でペアリングされます。

なお、ここで重要なのは trust しておくことです。 trust しておかないと、 再接続する時にまた操作が必要になります。

github から hw-key-remapper を clone

pi0w で以下を実行します。

※ 事前に golang 1.15 以降をインストールしておきます。

git clone --depth 1 https://github.com/ifritJP/hw-keyboard-remapper.git
cd hw-keyboard-remapper
go build

pi0w の USB Gadget を有効化

pi0w の /boot/config.txt に以下を追加します。

[all]
dtoverlay=dwc2

pi0w の /etc/modules に以下を追加します。

dwc2

注意

pi0w の RNDIS 通信を有効にしている場合、 以下を /boot/cmdline.txt に追加していると思います。

modules-load=dwc2,g_ether

この指定は外してください。 これを設定していると、 pi0w を HID キーボード化できません。

後のステップで、別の方法で RNDIS を有効化します。

なお、 以下の作業は RNDIS 経由の ssh ではなく、 WiFi 経由の ssh か、あるいは pi0w のコンソールで直接作業してください。

また、RNDIS 経由の ssh で作業していた場合は、 一旦 raspberry pi を再起動してください。

pi0w の USB Gadget で HID キーボードを登録

clone した hw-keyboard-remapper のディレクトリに移動し、 pi0w で以下を実行します。

sudo bash usb_gadget/rndis_hid.sh

これで、 pi0w が RNDIS と HID の複合デバイスとして構築されます。

以下のコマンドで、 hid デバイスと NIC に usb が pi0w 上に認識されていることが確認できます。

ls /dev/hid*
ip a

なお、 sudo bash usb_gadget/rndis_hid.sh を手動で実行するのは面倒なので、 /etc/rc.local に以下を追加します。

bash フルパスusb_gadget/rndis_hid.sh

rc.local ではなく、サービスとして追加するのがカッコいいのかもしれないですが、 usb_gadget/rndis_hid.sh は一度 on すると、 動的に off が正常にできないっぽいので、今回はカッコ良さは求めません。

pi0w に接続している PC が windows OS であれば、 この状態でコントロールパネルの「デバイスとプリンター」に、 次の名前のデバイスが登録されているはずです。

Linux USB Gadget/RNDIS+HID

これが RNDIS と HID の複合デバイスになります。

HID はドライバの設定等は不要です。 一方で、RNDIS を利用する場合は、別途ドライバの設定をしてください。

ドライバの更新 → 手動 → 一覧から選択 → ネットワークアダプタ → Microsoft → リモート NDIS 互換デバイス

なお、ここまで設定しておくと、 次回の ppi0w の起動時に「不明なUSBデバイス(デバイス記述子要求の失敗)」として 認識されますが、30秒程度で正常に複合デバイスとして認識されます。

キー変換プログラムを登録

キー変換プログラムは、次のモードを持ちます。

  • input event デバイス名のリスト出力
  • input event デバイスから入力されているキー情報出力
  • input event デバイスから入力されているキー情報を変換して HID キーボードとして出力

デバイス名の取得

まずは、以下を実行し「input event デバイス名のリスト出力」して、 デバイス名をメモっておきます。

sudo ./hw-keyboard-remapper -mode list

なお以下の場合、Keyboard のデバイス名がリストに出力されないので注意してください。

  • BT Keyboard がペアリングされていない
  • BT Keyboard が省電力モードに入って接続が切れている

なお、 vc4 がリストされますが、それはキーボードではなく VideoCoreIV チップです。

input event デバイスから入力されているキー情報出力

以下を実行し、キー入力を取得できているか確認します。

$ sudo ./hw-keyboard-remapper -mode scan -kb "XXXXXXXXXXXXXXX"

ここで、 "XXXXXXXXXXXXXXX" には先程メモしたキーボード名を指定します。

キーボードでキーを押すと、そのキー情報が出力されます。 例えば m j と入力すると、以下のような出力がされます。

DEBU[0002] [event] press key 50(0x32) KEY_M -> Keyboard m and M 
INFO[0002] data [0 0 16 0 0 0 0 0]                      
DEBU[0003] [event] release key 50(0x32) KEY_M -> Keyboard m and M 
INFO[0003] data [0 0 0 0 0 0 0 0]                       
DEBU[0003] [event] press key 36(0x24) KEY_J -> Keyboard j and J 
INFO[0003] data [0 0 13 0 0 0 0 0]                      
DEBU[0004] [event] press key 36(0x24) KEY_J -> Keyboard j and J 
INFO[0004] data [0 0 13 0 0 0 0 0]

ここで、 press key 50(0x32) KEY_M"m" の押下イベントが発生したことを示し、 50(0x32) は linux 側の "m" のキーコードを示します。 data [0 0 16 0 0 0 0 0] は HID コードの変換結果を示し、 3 バイト目の 16 は、 HID の "m" のコードを示します。

キーのカスタマイズは、この HID コードが重要になります。

この HID コードの詳細は、次の USB の規格書を参照してください。

なお、 linux のキーコードから HID コードへの変換がバグっている可能性は否定できません。 ローカルで修正するか、 issue で報告するか、 pull request してください。

hw-keyboard-remapper の終了

pi0w に ssh でアクセスしている場合、 Ctrl-C すればプログラムは終了します。

しかし、 pi0w のコンソールから実行している場合、 全てのキー入力がこのプログラムに取られて効かないため、 Ctrl-C も無効です。

この状態からプログラムを終了させる場合、 pi0w に接続しているキーボードから以下(qwe を4回)を入力してください。

qweqweqweqwe

これで終了します。

なおこの文字列は、 「偶然のキータイプでは発生せずに、簡単に入力できる文字列」として使用しています。

qweqweqweqwe が「普通に使う文字列だ」というのであれば、 適宜コードを変更してください。 (もしかしたら、 steam でゲームしてたら qwe を使うこともあるのかも?)

Key をカスタマイズ

単純に linux のキーコードから HID コードへ変換しても意味はないので、 HID コードを別のキーのコードに置き換えるように設定します。

置き換えは JSON ファイルで指定します。

JSON は次のような形式です。 リポジトリに config.json.sample を同梱しているので、適宜に編集してください。

なお、config.json を読み込ませるには、 -conf オプションで config.json のパスを指定してください。

{
    "InputKeyboardName": "XXXXXXXXXXXXXXXXXXX",
    "SwitchKeys": [
	{ "Src": 57, "Dst": 224, "Comment": "CaspLock -> L-Ctrl" },
	{ "Src": 224 , "Dst": 57, "Comment": "L-Ctrl -> CaspLock" }
    ],
    "ConvKeyMap": {
	"0x9": [ { "modMask": 1, "modResult": 1, "Code": 79, "modXor": 1,
		   "Comment": "C-f -> right arrow" } ],
	"0x5": [ { "modMask": 1, "modResult": 1, "Code": 80, "modXor": 1,
		   "Comment": "C-b -> left arrow" } ],
	"138": [ { "modMask": 34, "modResult": 2, "Code": 79, "modXor": 2,
		   "Comment": "L-SHIFT-MUHENKAN -> right arrow" },
		 { "modMask": 34, "modResult": 32, "Code": 80, "modXor": 32,
		   "Comment": "L-SHIFT-MUHENKAN -> left arrow" } ]
    }
}

JSON は以下の情報を持ちます。

  • InputKeyboardName
  • SwitchKeys
  • ConvKeyMap

InputKeyboardName

接続する BT Keyboard 名を指定します。

コマンドラインに -kb オプション指定がある場合、-kb オプションを優先します。

SwitchKeys

置き換える HID キーコードのペアを指定します。

  • Src

    • 置き換え元の HID キーコード。 integer。
  • Dst

    • 置き換え先の HID キーコード。 integer。
  • Comment

    • コメントです。変換には関係ありません。
  • On

    • この置き換え情報が有効かどうかを示します。 bool。
    • このキーが存在しないか、 true を指定した場合、有効として扱います。
    • 一時的に off にしたい場合に false を指定することを想定しています。

これは、単純にキーそのものを置き換えます。 例えば、 Ctrl と CapLock の置き換えのような時に利用します。

単純にキーそのものを置き換えるので、 「Shift を押しながら A を押した場合に、他のキーに置き換えたい」と いうような用途には使えません。

その場合は、 ConvKeyMap で指定します。

ConvKeyMap

ConvKeyMap は、 Ctrl や Shift などの modifier キーを押しながら他のキーを押した時の、 キーコード変換方法を定義します。

例えば C-f を押下したら → キーのコードを送る、なんてことも可能です。

ConvKeyMap は、次の要素からなります。

"ConvKeyMap": {
    "key1": [ {info}, ... ],
    "key2": [ {info}, ... ],
    ...
}
  • key

    • 変換元の HID キーコードを文字列で指定します。
    • 例えば f を押下した場合の動作を定義する場合、 "0x9" を指定します。
    • キーコードの表現は、16進数か 10 進数です。
  • info

    • 変換条件を次の配列で指定します。

      • modMask

        • modifier のマスク値を指定します。
      • modResult

        • modifier のマスク結果を指定します。
      • Code

        • 条件成立時の HID コードを指定します。
      • modXor

        • 条件成立時の modifier の XOR 値を指定します。
      • Comment

        • SwitchKeys と同じです。
      • On

        • SwitchKeys と同じです。

ConvKeyMap で指定すると、次の条件が成り立つ時に動作します。

(modMask & modifier) == modResult

例えば Left-Ctrl が押下されていることを条件にするには、 modMask と modResult 両方に 1 を指定します。

以下のサンプルは、 C-f が押下された場合、 に変換することを示します。

"0x9": [ { "modMask": 1, "modResult": 1, "Code": 79, "modXor": 1,
           "Comment": "C-f -> right arrow" } ],

ここで、modXor が 1 なので modifier に 1 が xor され、 結果的に出力される modifier の Left-Ctrl ビットが 0 になっていることに注意してください。

Shift キーが押された時のキーの入れ替えも同じように行ないます。

なお、 C-f に変換すると、 emacs では C-x C-fC-x → になってしまうので、変換はオススメしません。

変換プログラムの実行

以下を実行します。

$ sudo ./hw-keyboard-remapper -conf config.json -v

これで config.json で設定した remap が反映され、 pi0w に接続した BT キーボードの入力が、 pi0w と USB 接続している PC に HID キーボードの入力として通知されます。

上記コマンドで動作を確認したら、/etc/rc.local に追加します。

nice -n -5 パス/hw-keyboard-remapper -conf パス/config.json > /del/null &

他のプログラムによってキー入力処理が滞ると、 キーリピートなどの現象に繋りやすくなるため、 nice で 優先度を上げて実行しています。

/etc/issue の編集

前述の通り、このプログラムを実行していると pi0w のコンソール上でキー入力が効かなくなる。 当然ログインも出来ない。

ssh 経由であれば作業できるが、 ssh を接続できないケースがある。 その時、 qweqweqweqwe を入力すればキーボードが使えるようになるが、 そんな事は絶対に忘れるので、 pi0w の login プロンプトに警告を表示するように設定しておく。

/etc/issue に以下を設定しておくことで、メッセージが表示される。

=====> Keyboard is invalid now. To available the keyboard, enter "qweqweqweqwe".

Raspbian GNU/Linux 11 \n \l

なお、/etc/issue を編集する場合、 元のメッセージよりも上の行に設定せずに下の行に設定すると、 ssh 接続の鍵認証が出来なくなって、パスワード認証に切り替わってしまったので、 メッセージを編集する際は気を付けてください。

トラブルシューティング

dwc2 モジュールをロードした後、 USB Gadget に登録しないまま pi0w を PC に接続していると、 PC 側の USB 周りが不安定になることがありました。 「不明なUSBデバイス(デバイス記述子要求の失敗)」として認識されたまま、 放置するのが良くないようです。

この現象は環境依存かもしれませんが、dwc2 モジュールをロードした後は、 速やかに usb_gadget/rndis_hid.sh を実行してください。 rc.local に usb_gadget/rndis_hid.sh を設定しておけば、問題ありません。

なお、 usb_gadget/rndis_hid.sh は pi0w を RNDIS + HID デバイスにするスクリプトです。

RNDIS が不要で、 HID だけで良いという場合は usb_gadget/rndis_hid.sh の代わりに、 usb_gadget/hid.sh を実行すると HID だけを登録できます。

ただし、これも環境依存かもしれませんが、 usb_gadget/hid.sh 実行時も PC の USB 回りが不安定になりました。

よって usb_gadget/hid.sh は、 RNDIS を使いたくない人で、 かつ PC の USB 周りが不安定になるかどうか確認する人柱になっても良い人以外は オススメしません。