2013年11月20日水曜日

xmonadとHaskell(その10:ManageDocks)

(その4)辺りで「ステータスバーを付けてみる」と言いつつダラダラとメモしてきたので、いい加減ここらで具体例。

いつもの見慣れたスクリーンショットの様なステータスバーを付ける。


xmonadでステータスバーを表示するには外部アプリを利用する。
ここでは、表示用に「dzen」、情報取得用に「conky」。

ステータスバーは画面の上部に表示するが、このステータスバーは左右二つのdzenから構成されている。左側がxmonadからの出力を表示しているdzen、右側がconkyからの出力を表示しているdzen。


まずは、xmonad.hs
外部アプリの起動は、do構文を使ってmainで行う。

8行目でxmonadからの出力を表示するdzenを起動。spawnPipeを使ってハンドルを得る。

一方、9行目はconkyからの出力を表示するdzen。conky出力をパイプでdzenに繋いだシェルコマンドをそのまま実行するので、ハンドル(結果)は必要ない。そこで、これの実行については、spawnPipeではなく、spawnを使っている。

spawnPipe等に渡す引数はシェルで実行する文字列をそのまま文字列として渡せばよい。

dzenコマンドの引数の概略(詳細はhttps://github.com/robm/dzen

 -x  左端表示位置
 -w  長さ
 -ta 内容の表示位置、右寄せならr、左寄せならl

以下のものは左右のdzenともに共通なので18行目でdzen_styleとしてまとめて定義

 -h ステータスバーの高さ
 -fg フォアグランド色
 -bg バックグラウンド色
 -fn フォント名

xft用(?)のフォント名は「fc-list」コマンドを使ってシステムで使えるフォント名を調べる。サイズの指定は、そのフォント名にコロン「:」で繋げて「size=10」とかする。
また、普通のフォントの名前なら「xfontsel」コマンドとか使って調べられる。

そして、conky(http://conky.sourceforge.net/)
ネットで検索すると、デスクトップ上にグラフィカルに表示されたかっこ良いシステムモニタの画像がたくさん出てくる。

しかし、ここでは、この表示能力は使わず、その情報収集の機能のみを利用する。
上記xmonad.hsの9行目 "conky -c ~/.xmonad/conky_dzen_laptop" として呼び出している。
このconky_dzen_laptopの内容は以下の通り。
尚、/neko/home/.xmonad/icon/ディレクトリ内のアイコンファイルは、もともとdzenのサイトで配布されていたみたいなんだけれど、現在の配布は不明。しかし、githubで管理されているxmonad.hsの中によく入っているので適当に検索すると拾える。

さて、今までメモしてきたことは、dzenというアプリを画面上に表示してそこに情報を表示するということでしかなかった。

しかし、ステータスバーとして機能するためには、その表示用アプリであるdzen上にウインドウが重ならず常に見えていることが必要だ。

それを実現するための機能がxmonadには用意されている。

XMonad.Hooks.ManageDocksモジュール

このモジュールには3つの便利な機能が用意されている。

manageDocks

xmonadには、manageHookという機能がある。
簡単に言えばこれは、xmonad上でアプリケーションが実行されて新しいウインドウが開いた時、そのウインドウをどんなふうに扱うかを操作するための仕組みだ。
例えば、「firefoxが起動されたら、そのウインドウをwebというワークスペースに移動させる。」とか、「gimpが起動された時には、フローティング配置(タイリングしない)する」とかである。

この機能の設定は、XConfig l型データのmanageHookフィールドで行う。

で、ステータスバー的に振る舞わせたいウインドウ、すなわち、他のウインドウのようにタイリング表示されたり、特定のワークスペースだけに表示されたりするのではなく、常に一定の同じ場所で表示され続けるウインドウとして扱いたい旨の指定を簡単にしてくれるのが、このモジュールのなかの「manageDocks」という値だ。

上記xmonad.hsの15行目

manageHook = manageHook defaultConfig <+> manageDocks

XConfig l型データのコンストラクタをみるとmanageHookフィールドに設定するものはManageHook型のデータである。

ManageHook型データそのものの具体例については、別の機会にメモするとして、上の式の意味をみてみる。

式の読み方はだいたい、「manageHook defaultConfig」と「manageDocks」を演算子「<+>」で繋いだものであるという感じ。

まず、「manageHook defaultConfig」は、(その3)でもメモした「レコード構文で定義したフィールド名(項目名)は、その型のデータからそのフィールドの値を取り出す関数になる」というアレである。
つまり、defaultConfigとして定義されている値のmanageHook項目に入っている値を取り出している。そして、当然その値の型はManageHook型だ。

次に、「manageDocks」は、上で書いた通りステータスバー等の制御のためのデータが入ったManageHook型の値だ。

そして、演算子「<+>」はManageHook型の値を結合してひとつのManageHook型の値にするものである。

なので、15行目では、defaultConfigで定義されているmanageHookの内容に、manageDocksの内容を追加したものになる。

avoidStruts

xmonadには、layoutHookという機能がある。さまざまなタイリング、フルスクリーン、タブ等など。
おなじみのレイアウトであるが、どんなレイアウトを使うかは、XConfig l型データのlayoutHookフィールドで行う。

ステータスバーを使用する場合、ステータスバーの部分に他のウインドウが重ならないように配置を制御しなければならないが、このレイアウト制御をしてくれるのが「avoidStruts」だ。

上記xmonad.hsの14行目

layoutHook = avoidStruts $ layoutHook defaultConfig

layoutHookに定義すべきデータは(l window)型で、この型自体については、また別機会にメモ。
とりあえず、式の構造をみてみると、「avoidStruts関数」に「layoutHook defaultConfig」を引数として渡した戻り値となっている。

まず、「layoutHook defaultConfig」は先と同じで、layoutHookが関数であり、defaultConfigで定義されたlayoutHookフィールドの値が戻り値となる。

そして、avoidStrutsは関数であり、レイアウト制御のデータをとって、それに、ステータスバー用の隙間を開ける加工をしたレイアウト制御データを返すというものである。

docksEventHook

リファレンスの説明から具体的なイメージがよくわからないので、今回のxmonad.hsには含めてないが、使い方はmanageDocksと同じパターンで、XConfig l型データのhandleEventHookフィールドに定義すればよさげ。

handleEventHook = handleEventHook defaultConfig <+> docksEventHook

みたいにすれば、デフォルトのイベントフックにこの機能が追加される。


レコード構文を使った設定
初心者のころは、レコード構文のフィールド名が、データ型からそのフィールドの値を取り出す関数である、すなわち、同じ単語を使ってるけど意味が違うってのを知らないので、xmonad.hsが読めなかったりする。

しかし、これを理解して、改めて実際のxmonad.hsでの設定をみるとレコード構文の便利さが実感できる。

なんにせよ、xmonadの設定のあちこちでこれに似たパターンは使われるので

my_data = hoge_default { field_a = add_func $ field_a hoge_default }

みたいな形の理解は必須だ。

上下にステータスバー

スクリーンショットでもよく見られる上下のステータスバーは簡単にできる。
dzenコマンドに渡す引数の配置パラメータを少し変えて、左側dzenを上に、右側dzenを下にする。

8行目、-w 400をはずす

left_bar <- spawnPipe $ "dzen2 -x 0 -ta l " ++ dzen_style

9行目 -x 400 を変更して、-y 582を追加

...省略 | dzen2 -x 0 -y 582 -ta r " ++ dzen_style



しかし、S101はもともと画面の高さが低いので、両方のステータスバーを常に表示するとメインの表示領域が少なくなるから嫌だ!と考えたとしよう。

そんな時に役に立ちそうなのが、同じもジュール内にある「avoidStrutsOn」関数だ。上下左右のステータスバー領域を個別に設定できる。

avoidStrutsOn関数の一つ目の引数は、ステータスバーのための余白を開けたい方向を示す値(上はU、下はD、左はL、右はR)を要素としたリストとなり、二つ目の引数でレイアウトデータを渡す。

なので、上下にステータスバーがあるが上だけ常に表示したい場合は、以下の通り。

layoutHook = avoidStrutsOn [U] $ layoutHook defaultConfig



この方向を示すデータは、Direction2D型の値で、UとかDとかはいわゆる値コンストラクタだ(TrueとかFalseみたいなもの)。

Direction1D型のNext、Prevとともにxmonad.hsのカスタマイズのなかでよく出てくるのでリファレンスをチェックして頭の隅においておこう。
XMonad.Util.Typesモジュール

実際のところ

実際のところ、dzemanageDocksやdocksEventHookが効果出てるのかとかわからない。
まぁ、よそのxmonad.hsにはたいがい書いてあるので、おまじない的につけておけばOKなのかな。

それより、dzenの位置が画面の端から離れてると、avoidStrutsが効かないようなので注意。例えば、上で紹介した上下バージョンのステータスバーで下側のdzenの位置を -y 580とかするとavoidStrutsしても余白を作ってくれなくなる。

ついでに$演算子

$演算子は、割とどこでも説明されている「括弧を少なくするための記号」的なものだ。
解釈は、$の位置から最後までを括弧で囲んだのと同じ。なんて言う風に紹介されている。

つまり、上で紹介した

layoutHook = avoidStrutsOn [U] $ layoutHook defaultConfig

は、

layoutHook = avoidStrutsOn [U]  ( layoutHook defaultConfig )

となる。まぁ、そのおとりで簡単!

しかし、ここで、脱初心者的に$演算子の型シグニチャをみる。

($) ::  (a -> b) -> a -> b

まずは、$の左側(一つ目の引数)は関数で、右側(二つ目の引数)は「その関数に与える引数」が引数になるってこと。
そして、この演算子は右結合であるとともに、優先順位が最低なのだ。

すなわち、優先順位が最低ということは、必ず$記号が区切りとなるし、右結合なので、$のより右側が先に扱われるようになる。

だから、

a b c $ d e  $ f g $ h i

は、

a b c $(d e $(f g $(h i)))

になるとか聞くと、「へ~~~」ってなる。

ちなみに「$の位置から最後までを括弧で囲んだのと同じ。」という解釈は上手くいかなくて困ることもあるかもしれない。

例えば合成関数のときだ。

a . b . c $ d e

は、

a . b . c (d e)

という意味ではなかったりする。

何が違うかというと、(.)は右結合で、まずcを見ることになるが、その時、.演算子より空白結びつきが強いので

a . b . (c (d e))

となり、意図したのと違うものとなる。

実際には、$演算子を使うとその記号部分で区切られる(結合が弱い)ことになるので、あえて括弧を付けるなら

(a . b . c) (d e)

という感じになる。

0 件のコメント:

コメントを投稿