11月から仕事で Mac を使うようになって2ヶ月ちょっとたつけど、いまだにショートカットキーが Ctrl キーではなく Command キーであることに慣れない。
慣れないのは仕事以外で普段使ってる Linux と異なるからだと思うんだけど、普通に考えて Mac のショートカットキーの方が合理的だと思うので、Linux 上で Mac と同じような操作ができるようにした方が良いと思った。
というわけで Ruby で rkremap というのを作ってみた。rkremap はツールではなくライブラリなので、rkremap を使ったプログラムを作る必要がある。
まあ普通は「最強のキーリマッパー」の 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
ファイルを読むことでそのデバイスのキーイベントを読み取れる。
一般ユーザーには読み取る権限がないので root
かinput
グループに所属しているユーザーである必要がある。
詳しくは 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
+英字/Enter
を Ctrl
に変換してるほか、CapsLock
を Ctrl
として扱ったり、変換
/無変換
を長押しすると Alt
として扱ったり、日本語キーボードを英語キーボードとして認識させたときのキー配置を入れ替えてたりなどしている。
とりあえず動くところまでできたので、これでリリース。