Linux用キーリマッパー rkremap を作った

11月から仕事で Mac を使うようになって2ヶ月ちょっとたつけど、いまだにショートカットキーが Ctrl キーではなく Command キーであることに慣れない。

慣れないのは仕事以外で普段使ってる Linux と異なるからだと思うんだけど、普通に考えて Mac のショートカットキーの方が合理的だと思うので、Linux 上で Mac と同じような操作ができるようにした方が良いと思った。

というわけで Ruby で rkremap というのを作ってみた。rkremap はツールではなくライブラリなので、rkremap を使ったプログラムを作る必要がある。

github.com

まあ普通は「最強のキーリマッパー」の xremap を使うのがいいと思う。

作ろうと思ったのは xremap では(たぶんほかのツールも)日本語変換有効時を特別扱いできなかったのが発端なんだけど、YAML 等の設定ファイルを書くよりもプログラムで動きを書きたかったのでライブラリとして実装してみた。

以下、rkremap の使い方と背景の仕組みなど。

キーボード入力デバイスファイルを調べる

デバイスファイルは /dev/input/event[0-9]*。 キーボードに対応するデバイスは cat /proc/bus/input/devices でわかるかもしれない。 ThinkPad 本体のキーボードは event3 だった。

I: Bus=0011 Vendor=0001 Product=0001 Version=ab54
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/devices/platform/i8042/serio0/input/input3
U: Uniq=
H: Handlers=sysrq kbd event3 leds 
B: PROP=0
B: EV=120013
B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=7

sudo evtest /dev/input/event3 のようにしてキーを押して反応があればそのデバイスであることがわかる。

キーロガー

/dev/input/eventX ファイルを読むことでそのデバイスのキーイベントを読み取れる。 一般ユーザーには読み取る権限がないので rootinput グループに所属しているユーザーである必要がある。

詳しくは 1.1. Introduction — The Linux Kernel documentation を。

rkremap を使うとこんな感じでキー入力を読める:

require 'rkremap'
def code2key(code)
  Rkremap::CODE_KEY[code].to_s.sub(/\AKEY_/, '')
end
rk = Rkremap.new('/dev/input/event3')
rk.start do |code, mod|
  key = (mod.select{|_, v| v}.keys + [code]).map{|c| code2key(c)}.join('-')
  puts key
end

この場合はキーイベントは本来のアプリに渡されるのでアプリに影響を与えることなく、キー入力を読み取ることができる。

start のブロックは修飾キー以外のキーが押されたときに実行される。 ブロックの第1引数は押されたキーのコード(Integer)。Rkremap::CODE_KEY でシンボルに変換できる。 第2引数は修飾キーの Hash

「hoge!」と入力したときの出力:

H
O
G
E
LEFTSHIFT-1

通常使用しているユーザーを input グループに所属させると sudo とかの手間はなくていいんだけど、簡単にキーロガーを動かせちゃうので、セキュリティ的にはちょっと考えた方がいいかも。

Xアプリ名の取得

特定のアプリでだけ特殊な処理を行いたいとかで入力フォーカスがあるアプリの名前を知りたいことがある。

X11 の XGetInputFocus() でフォーカスしてる Window を得られる。 この Window はアプリのウィンドウではなくてアプリを構成している部品のようなもので、アプリのウィンドウを得るには、XGetClassHint() でクラス名を得られるまで XQueryTree() で親 Window を辿る。

…ということを Rkremap::App(Rkremap#start のブロックの第3引数)でできるようにしてある。ウィンドウタイトルは #title, クラス名は #class_name で得られる。

Ruby から使える X11 ライブラリにいいのが見つからなかったので、ffi を使って実装した。X11 みたいな巨大なライブラリの一部をつまみ食いするには ffi は便利。

この機能を使用するには Rkremap#x11 = true を設定する。

上のキーロガーの出力にアプリ名を追加するにはこんな感じ:

require 'rkremap'
def code2key(code)
  Rkremap::CODE_KEY[code].to_s.sub(/\AKEY_/, '')
end
rk = Rkremap.new('/dev/input/event3')
rk.x11 = true
rk.start do |code, mod, app|
  key = (mod.select{|_, v| v}.keys + [code]).map{|c| code2key(c)}.join('-')
  key << " at #{app.title} [#{app.class_name}]" if rk.x11
  puts key
end

Firefox 上の Google で「ほげ」を検索したときの出力:

HENKAN at Google — Mozilla Firefox [Firefox]
H at Google — Mozilla Firefox [Firefox]
O at Google — Mozilla Firefox [Firefox]
G at Google — Mozilla Firefox [Firefox]
E at Google — Mozilla Firefox [Firefox]
ENTER at Google — Mozilla Firefox [Firefox]
MUHENKAN at Google — Mozilla Firefox [Firefox]

仮想キーボードデバイス

/dev/uinput を使って仮想キーボードデバイスを作ることができる。

ioctl(UI_SET_EVBIT, EV_KEY) でキーボードデバイスを指定し、ioctl(UI_SET_KEYBIT) で入力可能なキーを指定し、ioctl(UI_DEV_SETUP) でデバイスのベンダーや製品バージョンを指定して、ioctl(UI_DEV_CREATE) で作るって感じ。

作った仮想デバイスに対してイベントデータを書き込むことでキーを入力したことにできる。

詳しくは 1.7. uinput module — The Linux Kernel documentation を。

rkremap は Rkremap#key でキーを押したことにできる。

以下は1秒毎に A〜Z のキーを入力する:

require 'rkremap'
rk = Rkremap.new("/dev/input/event3")
while true
  ('A'..'Z').each do |k|
    key = Rkremap::KeyCode.const_get("KEY_#{k}")
    rk.key(key)
    sleep 1
  end
end

キーリマップ

/dev/input/eventX に対して ioctl(EVIOCGRAB, 1) を設定するとキー入力イベントを奪い取ってアプリに入力が渡らなくなる。

そして読み取ったキーイベントに対して何かしらの処理後に /dev/uinput に書くことでキーイベントを書き換えることができる。

rkremap で、Firefox 上での Ctrl-N に置き換えるにはこんな感じ:

require 'rkremap'
include Rkremap::KeyCode
rk = Rkremap.new("/dev/input/event3")
rk.grab = true
rk.x11 = true
rk.start do |code, mod, app|
  if app.class_name == 'Firefox' &&  code == KEY_N && (mod[KEY_LEFTCTRL] || mod[KEY_RIGHTCTRL])
    mod[KEY_LEFTCTRL] = mod[KEY_RIGHTCTRL] = false
    code = KEY_DOWN
  end
  rk.key(code, mod)
end

ということで自分が使うために作ったプログラムはこちら https://github.com/tmtm/rkremap/blob/master/example/tmtms.rb

元々の目的だった Mac 風のショートカットにするために、Alt+英字/EnterCtrl に変換してるほか、CapsLockCtrl として扱ったり、変換/無変換 を長押しすると Alt として扱ったり、日本語キーボードを英語キーボードとして認識させたときのキー配置を入れ替えてたりなどしている。

とりあえず動くところまでできたので、これでリリース。

参考にしたもの等