2013年12月3日火曜日

xmonadとHaskell(その12: キーカスタマイズ)

前回は、xmonadのキーバインドの仕組みについて、defaultConfig のkeysフィールドを参考に見ながらメモしてみた。

今回は更に続けて、haskellでのキーボタンを表現する具体的な値に付いてメモ。

前回も見たが、一つ目のハッシュ
((modMask .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)

(modMask .|. shiftMask, xK_Return)が、キーボタンの組み合わせを表現している。
タプルの第一要素が修飾キー(winキーとかctrlキーとか)で、第二要素がキーシンボルの組み合わせだ。

修飾キー、キーシンボルで使える値は、Graphics.X11.Typesモジュールの中で定義されている。リファレンスは以下のページ。
http://hackage.haskell.org/package/X11-1.4.5/docs/Graphics-X11-Types.html

XConfig l型で定義されたkeysフィールドの型シグニチャによれば、修飾キーの型は、「ButtonMask型」であるが、これは「Modifier型」の型シノニムになっている。また、defaultConfigでのkeys定義では「KeysMask型」(これもModifier型の型シノニム)になっている。というわけで、よくわからんけれど、とりあえず、以下の値を使えば不都合無いと思う。

shiftMask   シフトキー
controlMask  Ctrlキー 
mod1Mask   altキー 
mod4Mask     windowsキー
0      修飾キーを押さない

最後は、もし、修飾キーを押さないことを表現したいときは、数字の0でよい。

上の例では、(.|.)演算子が見られる。これは、複数の修飾キーの同時押しを表現したい時に使う。なので、上の例はmodキーとシフトキーの同時押しを表している。

(.|.)演算子は、Data.Bitsモジュールにあるビット演算子で、ORを表している。
http://hackage.haskell.org/package/base-4.6.0.1/docs/Data-Bits.html
一般に、論理演算は、「AND」が「&&」で、「OR」が「||」だったりする。そこで、このビット演算はそのビットを「.(ドット)」に見立てて、「.&.」や「.|.」にしたのかな?と思うと面白い。

Modifier型の実態はCUInt型の符号なし整数であり、ソースを覗いて定義されている定数を見ると、ビット表現で同時押しを認識しているのがわかる。

さて、「mod1Maskとmod4Maskはネットの説明でも見かけるんだけど、それ以外の2とか3とかってなに??」と興味が沸いているかもしれない。そういうときは、まずは、以下のページをざっとみて、それからxmodmapを色々と調べてみると良いかも。
http://x68000.q-e-d.net/~68user/unix/pickup?xmodmap

次に、キーシンボルの方も上のリファレンスに値が羅列されている。
KeySym型の値は「xK_」で始まる名前になっている。特殊そうなキーもこのリファレンスで探せると思う。また、コンフィグアーカイブにある以下の人のxmonad.hsは、凄く沢山のキーバインドを定義しているので、キーの組み合わせ表現の参考になると思う。
http://www.haskell.org/haskellwiki/Xmonad/Config_archive/Nnoell%27s_xmonad.hs

キーカスタマイズ

今回は、具体的なキーカスタマイズの方法をメモする。
そして、カスタマイズする内容の例として、以下の二つを取り上げる。


xmonad.hsの再読み込みと再起動

前回紹介したように、デフォルトの設定のままのm-qのキー操作では、xmonadの再起動によってdzenが多重起動される場合がある。

defaultConfigで設定されるm-qの内容を見る(長いので折り返している)と

spawn "if type xmonad; \
     then xmonad --recompile && xmonad --restart; \
     else xmessage xmonad not in \\$PATH: \"$PATH\"; fi"

の様になっている。

spawn関数なので、その戻り値は、引数文字列をshellに渡して実行されるXアクションである。これにはエラー用の処理が施されているが、主となる部分は、再起動用のコマンドであり、それだけを取り出すと以下の通り。

spawn "xmonad --recompile && xmonad --restart"

これに、dzenの重複を避けるため、一旦、dzenプロセスを切るようにすることとして以下の様な、Xアクションにする。

spawn "killall dzen2; xmonad --recompile && xmonad --restart"

そして、これを、m-qに割り当てるカスタマイズをする。

xmonadのヘルプ

さて、もうひとつ。defaultConfigのkeysフィールドの中で

((modMask .|. shiftMask, xK_slash ),
  spawn ("echo \"" ++ help ++ "\" | xmessage -file -"))

及び

((modMask, xK_question ),
  spawn ("echo \"" ++ help ++ "\" | xmessage -file -"))

と定義されている部分がある。

これは、「M-shift-/」また「M-?」で、ヘルプ(キーバインド一覧)が表示されるものである(知ってた?)

ヘルプの内容は「help」という名前で定義された文字列であり、これはdefaultConfigのソース内の最後のほうで定義されている。そしてこれは、単なる文字列で設定されているだけなので、キーカスタマイズしても自動的にヘルプの内容が変わるわけではない。

というわけで、この機能を削除しても特段、支障はないので、このキーをキーバインドから削除するための練習用に使ってみる。

以上の2点である。

そして、巷のxmonad.hsをみると、キーカスタマイズの方法には幾つかの異なる方法があることがわかる。

そこで、今回のメモでは、巷のカスタマイズが読み解けるように、上記2点のカスタマイズを実現するための複数の方法を紹介する。

defaultConfigベースのコピペ型

xmonadでキーバインドのカスタマイズする時、一番根本的な方法はXConfig l型のkeysフィールドを直接書いて、全てのキー定義をしてしまうことである。そして、その具体的な方法の一つは、defaultConfigのソースからkeysフィールドの定義すべてをコピペし、必要な部分を書き換えたり追加したりする方法だ。

「一部分のカスタマイズのために、キーバインド全部の定義をしなおすのは、大げさだ!」とも思う。しかし、実際は「コピペ」なので、事実上の手間はかからない。
また、必然的に知らないキーバインドが無くなり、また、初心者の時には書式に親しむことにもなるため選択肢としてはアリだと思ったりする。

ソースの中では、keysの定義をコピペして、その名前をmykeysとした。
設定の変更については、直接該当項目を書き換え、削除は、単にその項目を削除する。

XMonad.Util.CustomKeysモジュールの利用

xmonadのモジュールの中には、キーカスタマイズを簡単にするための関数を配布しているものがある。その一つがXMonad.Util.CustomKeysモジュールだ。

リファレンスは以下のページ
http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Util-CustomKeys.html

このモジュールを使ったカスタマイズは、主としてcustomKeys関数を使って行う。

この関数は、defaultConfigに設定されているキーバインドをデフォルト設定とした上で、そこから削除したいキー操作のリストを返す関数と、追加したいキー操作、Xアクションの組み合わせのリストを返す関数を引数として与えると、XConfig l型のkeysフィールドに定義すべき関数を返してくれるものである。(また、customKeysFrom関数をつかえば、カスタマイズされたXConfig l型データがある場合それをデフォルト設定とすることも出来る)

ここでまず、customKey関数の型シグニチャを読んでみよう。

関数の型シグニチャの中で括弧があると、その部分はひとまとまりの関数、すなわち、関数を引数としてとることを表している。なので、この関数は、二つの関数を引数としてとる。

そして、その続きの部分をよくみると、keysに設定するための関数、すなわち、前回から何度も見ている

 XConfig Layout -> Map (keyMask, KeySym) (X ())

となっている。すなわち、二つの関数を引数にとって、keysに設定する関数を返してくれることがわかる。

矢印を見る目が変わってくると、カリー化された関数の部分適用が実際に活用されている部分に気付くことができるようになり、「おおおおお~!部分適用ってこうやって使われるのか!!」って興奮できるかも。

あらためて各引数をみてみる。

削除する関数部分

一つ目の引数は、いらないキーバインドのリストを返す関数である。

(XConfig Layout -> [(KeyMask, KeySym)])

削除したいキーボタンを、修飾キーとキーシンボルの組み合わせのタプルで表現し、そのリストを返す関数だ。

ここでは、「M-S-/」と「M-?」を削除する関数delkeysを作ってみる。
Modキーについては、引数として渡されるデータから読み出すためレコード構文型のパターンマッチを使う。下の例では「modkey」という名前が仮引数になる。

delkeys XConfig {modMask = modkey} = [(modkey .|. shiftMask, xK_slash)
                                     ,(modkey, xK_question)]

追加する関数部分

二つ目の引数は、追加したいキーバインドのリストを返す関数である。

(XConfig Layout -> [((KeyMask, KeySym), X())])

一つ目の関数と違って、キーボタンの組み合わせと、動作させるX()型のデータとのタプルをリストにしたものだ

今度は、addkeys関数と名前をつけた関数を作って、m-qの内容を変更する。ここで登録する関数は、追加だけでなく、既にキー登録されているものはその内容が上書きされるので、内容の変更もこの関数登録で行えばOK。
引数のパターンマッチはdelkeys関数の定義と同様。

addkeys XConfig {modMask = modkey} =
    [( (modkey, xK_q)
    , spawn "killall dzen2; xmonad --recompile && xmonad --restart")]


以上をまとめた形でxmonad.hsに組み入れると以下のようになる。

keysフィールドの定義をcustomKeys関数の部分適用で行い、また、上記で説明した、引数となる関数「delkeys」,「addkeys」をmain関数にローカルな名前としてwhere構文を使ってみた。

defaultConfig{}の各フィールド値の定義については、短いものだと直接{}の中で定義してしまうこともあるし、設定ファイルによれば、わざわざ全部に「myなになに」と名前をつけて、別の場所で定義しているものもある。混乱しない限りは、あまり統一しすぎた形式にこだわらず、長ければ、名前をつけて外で定義する、短ければ中で直接書くって感じで、臨機応変に見やすさを選んでも良い感じ。where構文を使えばローカル定義になるので、名前の重複を気にしなくても良い。

さて、customKeys関数を使う方法は、先のdefaultConfigコピペ型と比較すると、設定変更を行う部分のみが設定ファイルに書かれることになり簡潔に表現できる。また、Map化自体は関数がしてくれるので、データの表現自体は組み合わせのリストでよい。

XMonad.Util.EZConfigモジュールの利用

まずはリファレンス
http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Util-EZConfig.html

このモジュールを使ったカスタマイズの特徴として、キーボタン操作を"M-q"という風に一般的によくみられる文字列(リファレンスではemacsスタイルと書かれている)で表現することができるので、意味が分かりやすくなる。

もう一つの特徴は、このモジュールのカスタマイズ用関数の戻り値は、XConfig l型のkeysフィールドに定義する関数ではなく、keysフィールドが定義されたXConfig l型そのものを返す。

すなわち、ベースとなるXConfig l型データを受け取り、カスタマイズされたXConfig l型データを戻す関数だ。これは、xmonadでのカスタマイズで見受けられる設定のパターンのひとつだ。

例えば、(その4)でステータスバーを付けるためにdzen関数を使ったがこれも似たパターンだ。dzen関数は、Xアクションを返すが、その結果部分についてだけ見れば、引数で受け取ったXConfig l型データについて、dzen用のカスタマイズとしてフィールドを書き換えたXConfig l型データを返すものだ。

また、このモジュールでは、追加用(既にある場合は上書き)の関数と削除用の関数が別々に定義されているので、必要なものだけを使うことも出来る。

まず、このモジュールで主となる関数は以下の追加用の関数だ。

additionalKeysP :: XConfig l -> [(String, X ())] -> XConfig l

二つ目の引数に着目すると、文字列とXアクションがペアになったタプルのリストである。
この文字列部分が、キーボタン操作を表現する部分で、emacsスタイル、すなわち、"M-q"と表現することが出来る。

また、削除用の関数は以下のように、引数のリスト部分はキーボタン操作を表現した文字列のみだ。

removeKeysP :: XConfig l -> [String] -> XConfig l

尚、このモジュールにはキー操作部分の表現に従来通り(ButtonMask, KeySym)を使えるadditionalKeys関数とremoveKeys関数も定義されている。


さて、このモジュールの関数を使った典型的なキーカスタマイズ例は以下の通り。

巷のxmomad.hsで見受けられる中置関数化してXConfig l型データの後ろに引っ付けるやつだ。

ここで、このモジュールを使う場合の留意点を幾つかメモ。

emacsスタイルの表現

emacsスタイル表現の文字列について、まず、修飾キーの表現は以下の通り。

 M-   modキー
 C-   ctrlキー
 S-   シフトキー
 M1-  altキー
 M4-  windowsキー等(スーパーキー)
ここで、modキーというのは、引数となるXConfig l型でのmodMaskで定義されているキーだ。

次にキーシンボルはそのままの文字を書けば良い。但し、リターンキーやカーソルキーなど特殊なキーの表現については、<Return>や<KP_Up>という表現がなされるので、リファレンスからみつける。

更に、xmonadのキー操作においては、サブマップという仕組みを使うことで、連続したキー入力を受け付けることが可能になる。例えば、「Ctrl+x」と押した後に、「a」を押すというような操作方法だ。

そして、サブマップの機能は、additionalKeysP関数を使うと簡単に実現できる。
連続キー入力の表現の仕方は、emacsスタイルを使って、単に、続けて押したいキー操作とキー操作の間にスペースを置き、"C-x a"と表現する。

なお、このサブマップの仕組みは、キー操作だけの単純な問題ではなく、実際にはXアクションの中に更にキー操作を受け付けるXアクションを入れた、入れ子になった構造を利用していることを頭の片隅に置いておくとトラブルを避けやすいと思う。(XMonad.Actions.Submapを参照)

mkKeymap関数の利用

additionalKeysP関数等、上記でみた関数は、カスタマイズされたXConfig l型を返す関数だった。
しかし、実はこのモジュールにもXConfig l型データのkeysフィールドに設定する関数を作るのに便利な連想配列を返す関数も定義されている。それがmkKeymap関数だ。

mkKeymap :: XConfig l -> [(String, X ())] -> Map (KeyMask, KeySym) (X ())

型シグニチャをみると見慣れたMap型が戻り値となる関数だということがわかる。
そして、この関数はemacsスタイル表現で[(String, X ())]部分のキーバインドリストを作成し、これをkeysで読めるMapに変換してくれる関数なのだ。

ここで、keysフィールドの型は

 keys :: !(XConfig Layout -> Map (ButtonMask, KeySym) (X ()))


なので、この関数を使って次のようにすればkeysフィールドに設定する関数を定義できる。

  mykeys conf = mkKeymap conf [("M-a",hogehoge)
                              ,("M-b",hagehage)
                              ,...]

但し、この場合、キーバインドの追加削除でなく、keysそのもの、すなわち、全てのキーバインドの設定をしなければならない。

checkKeymap関数によるチェックの仕方

更に、このモジュールには、emacsスタイルの表現が間違っていないかどうか等をチェックしてくれる便利なユティリティ関数checkKeymapがある。

checkKeymap :: XConfig l -> [(String, a)] -> X ()

引数は、一つ目の引数にXConfig l型データ、二つ目の引数にキー操作、Xアクションのペアのリストである。なので、この関数によるチェックをする時には、additionalKeysPの引数で直接にキーバインドのリストを書くのでなく、別途名前を付けたリストとして定義しておくと便利だ。

戻り値のXアクションは、それが実行されると、emacsスタイルの表現がおかしくないか、キーバインドが重複していないかをチェックして、もし、おかしい場合にはxmessageアプリをポップアップしておかしな箇所を指摘してくれるものだ。

リファレンスによればこの関数を置く場所に相応しいのはXConfig l型データのstartupHookフィールドだと書かれ、以下のような例が書かれている。(mykeysがキーバインドのリスト)

myConfig =
  defaultConfig { .... 
                , startupHook = return () >> checkKeymap myConfig mykeys
                , ..... }

そして、注意書きとして「return () >>」を書いておきなさいと書かれているようなので(再帰定義の無限ループに落ちる?)、指示にしたがっておくほうが良さそうだ。

さて、巷のxmonad.hsを眺めるとキーコンフィグに関してはadditionalKeysP関数を利用し、checkKeymap関数でチェックを掛けているものが多くみられるように感じる。そこで、今回のメモのまとめとして、dzen呼び出しをした(その10)のxmonad.hsをベースに以下のようなxmonad.hsを例示してみる。


ghciで名前定義するときに使われる「let」。
実は、「let式」といわれるもので、「in」と対になっており、let式としての値は、in以下の評価値となる。

 ans = let a = 1
           b = 2
       in a + b

は、ans = 1 + 2 と同じだ。

例のmyConfigの定義は、c(configのc)でキーカスタマイズ以外のコンフィグを行い、inの部分で、cをベースにadditionarlKeysP等を使って、キーカスタマイズを織り込んだXConfig l型データを作っている。

これは、コンフィグアーカイブの一番初めに紹介されている/adamvo's xmonad.hsでそんな定義が使われている。初め、意味が分からなかったのだが、読めてみると凄くかっこよい定義に見えたので真似てみた。

myAddkeysの定義でXConfig l型を引数にしているのは、modキーのカスタム値は別として、他のXConfigのフィールド値を受け取って、Xアクションで活用することが出来るようにしておくため。(terminal名とか)

0 件のコメント:

コメントを投稿