LoginSignup
17
19

More than 5 years have passed since last update.

Linuxでのキー割当ツール作ってみた

Posted at

気づいたら3ヶ月くらい何も書いてませんでした・・・。

さて、皆様キー割り当てツール(または配列エミュレータ)などは利用しているでしょうか。何やそれ?という方は、あんまりいないんじゃないかとは思いますが。

例えば次のような用途で利用されるかと思います。

− SandSをやりたい
− CtrlとCapslockを切り替えたい
− 変換とか無変換とかにいろいろ割り当てる
− 新しいかな入力を使う

Windowsの場合、CtrlとCapslockの切り替えについては、普通にやるとレジストリをいじるしかないんですが、そういったことが出来ない場合は、レジストリを変更しないツールを利用するっていうのがふつうだと思います。また、それ以外となると、ソフトウェアなりハードウェアなりで実装されてないとそもそも使えないので、ツールを利用する必要があります。

さて、ではこういったツールというのはどんなものがあるのかというと。

私が把握してる限りはこんな感じになります。MacとLinuxで利用できるツールが明らかに不足してますね・・・。特にLinuxでのツール不足は深刻で、Windowsのような使いやすかったり機能が豊富だったり、というものがほとんど見つかりません。

Linuxで別のかな入力を使いたい

私のメインPCはLinuxなので、当然ながらLinux上で日本語入力をやることになります。ちょっと前に、キーボードをErgodox EZに変えた後、色々効率とか手の疲労とかを考えるようになり、自分が使う配列自体を変えています。

利用している配列は Colemak配列 になります。これ自体はErgodoxのファームウェアを書き換えて実現しているので、環境を問わず利用できます。このへん、Ergodoxが強いなーと思います。

しかし問題なのは日本語入力です。いままではローマ字入力でしたが、単純に日本語入力の効率だけを見るのであれば、1キー=1文字となる、かな入力の方が効率がいいのは明らかです。ただ、これが調べてみると、かなり群雄割拠的な様相を呈しています。

有名(?)な親指シフトを始めとして、派生系も含めて様々な配列が提案されています。何個か試用してみて、 新下駄配列 という配列を利用することにしました。詳しくは作者のHPを参照してもらうのが早いんですが、 同時打鍵 が特徴になっています。

さて、新下駄配列はWindows/MacOSXだとなんとか利用できるんですが、Linuxとなると、恐らく 窓使いの憂鬱 for Linuxを利用した例以外は存在していないと思います。まぁ私もそれを使えばいいんですが、

− 窓使いの憂鬱のシンタックスが好みじゃない
− メンテナンスがかなり前から止まってる

ということがあり、利用には二の足を踏んでいました。そして、ふと

「ないんだったら作ってみればいいじゃない」

と言う声が聞こえたので、勉強を兼ねて作ってみました。

Linux上でのキーフック

窓使いの憂鬱のような、キーボードのキー入力をフックするようなソフトウェアをLinux上で作る場合、基本的にはキーボードドライバから情報をよみとる必要があります。ただし、普通にやると、カーネルモジュールなどを作成して〜というように、かなり大掛かりになります。

実はLinuxには、バージョン2.2から、 Input Subsystem という仕組みが提供されています。これ自体は、色々な入力デバイスを抽象化するための仕組みですが、この中に、 User Input というモジュールが含まれており、これを利用することで、 キーボードなどの入力を自由にカスタマイズできます

この辺、詳しくは以下のURLを参照してください。サンプルコードまで載っていて、非常に参考になります。

さて、いくつかこういったサンプルとかを見ると、大抵というか99%というか、CとかC++で書かれたものばっかりです。Linuxカーネル自体がCで書かれていること、デバイスレベルのプログラムでポインタが色々出てくること、などが要因だとは思います。
しかし、このご時世、NULL pointerと戦いたくありません。なので(?)、OCamlで書いてみることにしました。

実際作ってみたのが↑です。

OCamlでの低レベルプログラミング

ここからは、OCamlでこういったものを作る場合の注意点とかです。

OCamlで低レベルプログラミング(ポインタなどを利用する必要がある)をする場合、大きく2つの選択肢があります。

  1. 自分でCを使ってFFIを書く
  2. Ctypesを利用する

場合によっては 1. を利用しなければならない場合もありますが、今は基本的に Ctypesを利用します。

Ctypes - https://github.com/ocamllabs/ocaml-ctypes

Ctypesを使うと、OCaml上で次のような形で宣言出来ます。

let ioctl = foreign "ioctl" ~check_errno:true (int @-> int64_t @-> ptr void @-> returning int)

なんとなく何やってるのかはわかるんじゃないかと。ただ、これを実際に利用するときは、ここまでわかりやすくはありません。

例えば、標準ドライバに処理を渡さないために、GRABという設定をする必要がありますが、これは↑を使ってこんな感じに書けます。


let flag = Nativeint.of_int 1 |> ptr_of_raw_address in
Inner.ioctl fd Signed.Int64.(of_int 1) flag |> ignore;

値のポインタ を渡すわけではなく、 値をポインタ として渡す必要があるので、こういう形にする必要があります。

Ctypesは、OCaml上の値とポインタとの橋渡しを行うための各種関数を提供してますが、この辺は自前で行う必要があります。

しかし、それを考えたとしても、それ以外の場所は型で守られているし、実際 C 側の関数は一部でしか使いませんので、大きな問題にはなりません。

定数マッピングめんどくさい問題

Ctypesで、C とのバインディングをいくつか作成していると、こんなことを感じると思います。 めんどいぞ! と。

C言語では、Enumまたは define プリプロセッサ で定数を定義するのが一般的ですが、どっちにしろコンパイル時にしかわからないうえ、OCaml側がそれを読み取ることは(基本的に)できません。やりたければ、自分でヘッダファイルからコピーしてくるか、値を取得するためのバインディングを自作する必要がありました。

しかし、現在はこれも(手間がかかるとはいえ)自動的に作成することが出来ます。

(* ffi_bindings.ml *)
open Ctypes

module Types (F: Cstubs.Types.TYPE) = struct
  module Event_type = struct
    let ev_syn = F.constant "EV_SYN" F.int
    let ev_key = F.constant "EV_KEY" F.int
    let ev_rel = F.constant "EV_REL" F.int
    let ev_abs = F.constant "EV_ABS" F.int
    let ev_msc = F.constant "EV_MSC" F.int
    let ev_sw = F.constant "EV_SW" F.int
    let ev_led = F.constant "EV_LED" F.int
    let ev_snd = F.constant "EV_SND" F.int
    let ev_rep = F.constant "EV_REP" F.int
    let ev_ff = F.constant "EV_FF" F.int
    let ev_pwr = F.constant "EV_PWR" F.int
    let ev_ff_status = F.constant "EV_FF_STATUS" F.int
    let ev_max = F.constant "EV_MAX" F.int
    let ev_cnt = F.constant "EV_CNT" F.int
  end
end

まずはこんなモジュールを作ります。これはUser inputで利用されるイベントの種類をマッピングしようとしてます。

これを利用して、次のようなソースから実行ファイルを作ります。

open Ctypes

let () =
  print_endline "#include <linux/input.h>";
  print_endline "#include <fcntl.h>";
  print_endline "#include <linux/uinput.h>";
  Cstubs.Types.write_c Format.std_formatter (module Ffi_bindings.Types)

作った実行ファイルを起動すると、Cのソースが吐き出されるので、更にそのソースをコンパイルして実行します。

すると、最初のファイルに対応する定数と、元々のモジュールの宣言をマッピングするためのモジュールが生成されます。

それを利用することで、各環境で、定数の値がずれているから動作しない、ということが無くなるうえ、自分でバインディングを書く必要も無くなるなど、いいことづくめです。多少ビルドが複雑になりがちというのが欠点といえば欠点かもしれません。

何故か公式のドキュメントには明記されてないようですが、利用できるなら利用してしたほうがいいかと思います。

okeyfumで実装した機能

既存のツールにおける設定ファイルの形式などを参考に、ひとまず新下駄配列を実装するための機能として、次のような機能だけ実装してます。

  • ロック状態の定義
  • 変数の定義
  • キーシーケンスの定義
    • シーケンス・変数内で実行可能な関数の定義
  • ロック状態でのキーシーケンス定義

これくらいあれば、新下駄配列だけでなく他のかな配列も実装できました。ただ、他のキー割り当てツール(または配列エミュレータ)にあるような以下の機能は、制約上実装していません。

  • 修飾キーを組み合わせたシーケンス
    • 難しくはないですが、必要性を感じないので実装してません。
  • 他ファイルの読み込み
    • 本来であればつけたほうがいいとは思いますが、無くても特に困らなかったので実装してません
  • シーケンスから直接日本語を適用
    • uinputデバイスから、直接日本語を出力することは出来ません
  • IME状態の検出
    • 仕組みがいくつかあって、それぞれに実装する必要があるので、必要がない限りは実装することはないと思います。

設定ファイルはこんな感じです。今使ってるファイルの総行数は2000とかなってるので、ごく一部ではありますが、雰囲気はつかめるかと思います。

key up compose = compose,&switch()

# 新下駄配列
deflock l1
deflock l2
deflock l3
deflock l4
deflock l5
deflock l6

defvar $clear       = &unlock(l1),&unlock(l2),&unlock(l3),&unlock(l4),&unlock(l5),&unlock(l6)
# 各キーのロック状態を表す。l1〜6は、それぞれbitの位置に対応し、6bit = 64種類のlockを定義できる
defvar $L_q         = &unlock(l6) ,&unlock(l5),&unlock(l4),&unlock(l3),&unlock(l2),&lock(l1)
defvar $L_w         = &unlock(l6) ,&unlock(l5),&unlock(l4),&unlock(l3),&lock(l2),&unlock(l1)
defvar $L_f         = &unlock(l6) ,&unlock(l5),&unlock(l4),&unlock(l3),&lock(l2),&lock(l1)

...

lock l6 {
  default up = 
  key up _1          = _1,$clear

  key down _1        = _1,$L_1
  key down _2        = _1,$L_2
  key down _3        = _1,$L_3
  ...
}

ちなみにこの文章を書いてる時も、このツールを使って書いてますが、非常に安定してますし、ほとんど負荷もありません。デバイスの抜き差しさえしなければ、休止状態から復帰しても問題ありませんでした。

やってみよう。高レベル言語で低レベルプログラミング

今回、ほぼ自分専用にツールを作ってみたわけですが、OCamlに限らず、ポインタを触らずにすむ高レベル言語で、デバイス周りを書くっていうのは、なかなかいいんじゃないかと思います。

今回の目的であれば、RustでもGolangでもいいと思いますが、それらはそもそもそういう目的のために作られてるので、それだと面白くないかなーと。

個人的には

OCaml > Rust >> Golang >= C

みたいな感じです。Golangは何というかこう。。。

OCaml + Ctypesであれば、FFIバインディングにOCamlのパワーを注入することができるので、CでしかライブラリがないけどOCamlで書きたいものがあれば、試してみてはいかがでしょうか。

17
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
19