xmonadの設定の基本は、xmonad関数の引数となるXConfig l型のデータを定義することです。 そして、その際には、XConfig l型データを一から定義するのではなく、 XMonad.Configモジュールで定義されたデフォルトデータであるdefをベースとし、 レコード構文を使って必要な部分のみをカスタマイズする形を取るのが一般的です。
このようなカスタマイズの方法に従ったxmonad.hsの雛形は、以下のようなものになります。
ここでは、この雛型を基にして、xmonadのキーバインドを司るkeysフィールドの定義を行います。
keysフィールド
xconfig l型のkeysフィールドには、 「xmonadの設定データを受け取って、キーとアクションのペアデータを返す関数」を定義します。
keys = \c -> mkKeymap c (keyMapDataList c)
ここで実際に定義されている関数は、ラムダ式(その場で関数を定義する書式)を使って書かれており小難しく見えますが、 これは定番の形であり、カスタマイズの際でも今のところこの部分を積極的に書き換える必要はありません。 ですから、この部分の式の意味がよくわからなかったとしても、特に心配しなくても大丈夫です。
この式では、keyMapDataList c
の部分が、
キーの組み合わせとアクションがペアになったタプルのリストを表していて、
その実体が、少し後ろの「キーバインド関連」の部分で定義されています。
キーの設定をするためだけなら、初めは難しく考えなくても、
この「キーバインド関連」のリストの中だけを書き換えるだけでカスタマイズを始められます。
XMonad.Util.EZConfigについて
「キーバインド関連」の部分を見てみましょう。一番初めのタプルの内容は次のようになっているはずです。
(“M-S-<Return>”, spawn $ XMonad.terminal conf)
キーの組み合わせは"M-S-<Return>“となっています。”(二重引用符)を使って文字列として定義している事に注意しましょう。 "M-"はModキー、"C-"はコントロールキー、"S-"シフトキーの同時押し(修飾キー)を表します。 組み合わせる通常のアルファベットや数字は、そのまま直接書きます。 また、<Return>に見られるような特殊なキー等の表現の仕方は、次のページに紹介されています。
https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Util-EZConfig.html
また、この表記方法で"M-"はxmonadの設定で、modMaskフィールドに設定されたキーとなりますが、 "M1-"とすると直接altキーを指定し、"M4-"で直接Windowsキーを指定することが出来ます。 更に、間にスペースを入れることで、キーの連続入力を定義することも出来ます。 以下に、例を示します。
-- Mod + pを押してからaを押す
(“M-p a”, spawn "dmenu_run")
-- Mod + pを押してからbを押す
(“M-p b”, spawn "gmrun")
-- Modキーとしてaltを直接指定
(“M1-p”, spawn "gmrun")
さて、巷のxmonad.hsでのキーの設定では、キーの組み合わせについて、次のような表現がされているものを目にする事が多いと思います。
((modMask .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)
本来、XConfig l型で受け入れる、キーの組み合わせの表現は、こちらなのですが、
ユーザーがより直感的に読みやすくするための表記が使えるように、
XMonad.Util.EZConfigモジュールがmkKeymap
関数を準備してくれています。
そこで、このxmonad.hsではこれを使っています。
どちらの方が優れているというものは在りませんが、混在はさせれないので、
他のxmonad.hsからキー設定をコピーする時には注意が必要です。
引数にxmonad設定データをとるよくあるパターン
keyMapDataList
に、キー組み合わせと、アクションのタプルのリストを定義するだけなのに、
定義の式をよく見ると、わざわざ、conf
という引数がついています。
haskell初めての人には、余計なものがついていて複雑に見えると思いますが、
これはよくあるパターンなので、理解して慣れましょう。
先にみたターミナルエミュレータを起動するキーバインドの設定を見てください。
アクションの部分にspawn $ XMonad.terminal conf
とかかれて、引数のconfが使われています。
ターミナルエミュレーターは、defをレコード構文で書き換えて好きなものを設定しています。
しかし、xmonadでは、何が設定されているかを知るためには、xmonad.hsの中の"terminal"という「変数名」参照して知ることは出来ないのです。
そもそも、"terminal"は、変数名でなくフィールド名であり、他の言語の変数の様なふるまいはしません。terminal自体は、xconfig l型データから、
terminalフィールドの値を取り出すための関数として振舞うことができます。まさにXMonad.terminal conf
の部分です。
このような、haskellのレコード構文に不慣れな方は「おしゃれなレコード構文」も参照してみてください。
xmonad.hsの中では、xmonadの設定がどの様に設定されているかが必要な部分については、 このように、「xmonadの設定を受け取って、設定結果を返す関数」を設定するようになっている部分が多いのです。 「 関数を設定する」と聞くと、すごく複雑なように聞こえますが、 単に、xmonadの設定を受け取って、それを自分の定義の中で使えるようにしてくれているだけであり、 haskellの書式的にも関数定義は単にイコール記号の左に引数文字が付くだけなので、 慣れると関数であることを意識しなくなります。
キーの設定とは
さて、キーの設定といえば、例えば、フォーカスの移動を通常の「Mod + j」では無くて、 自分の押しやすい好きなキーの組み合わせに自由に変えるということを思い浮かべるかもしれません。 しかし、実際のxmonadでのキーの設定の醍醐味は、 自分の好きなキーに、自分の好きなhaskellのアクションを割り当てることにあります。 ここで、雛型のxmonad.hsで設定されているタプルのリストを眺めて、どんなアクションがあるのか見てみましょう。
- spawn関数による、起動系
- sendMessage関数による、レイアウト制御
- windows関数等による、ワークスペースやフォーカスの制御
- それ以外
ここでは、見慣れたアクションしかありませんが、 xmonadの提供するモジュールには、もっと面白そうなものもあります。 そして、これはhaskellのアクションなのですから、自分でプログラムすることも可能なのです。 しかし、まずは、keysでキーに設定しているのは、アクションである事と、 そのアクションには色々提供されているものがあり、それらを好きに呼び出すように設定することができるということを把握しましょう。
ランチャーの設定とspawn関数
(“M-p”, spawn “dmenu_run”)
これは、「Mod + p」で、dmenuを呼び出す設定です。 タプルの1つ目の要素がキーの組み合わせで、 2つ目の要素がそのキーを押した時に実行されるアクション等を定義しているのは先ほどから述べている通りです。
ここでみられるspawnは関数であり、 その引数で渡された文字列を実行するというアクションを返します。 デフォルトで使われるdmenuがちょっと地味だと思うならば、 spawnに渡す引数を書き換えるだけで、 自分の好きなランチャーを呼び出すことが出来ます。 試しにgmrunを使ってみましょう。
(“M-p”, spawn “gmrun”)
もちろん、spawn関数は、ランチャーしか起動できないわけではなく、何だって実行できます。 文字列は、シェルに渡されるので、通常のプロンプトに書き込むように、コマンドにオプションも付けてOKです。 つまり、このパターンさえマスターすれば、まずは、ボタン一発でアプリを呼び出す設定を好きなだけ作れるようになります。
レイアウトとメッセージ
ここでは、レイアウトそのものについては深く踏み込みこまず、 レイアウトは「メッセージ」というもので操作できるということを把握します。
まず、デフォルトでxmonadのレイアウトには「Tall」「Mirror」、「Full」の3種類があり、 これらを「Mod + space」で入れ替えられる事は、既に知っていると思います。 そして、実際にこれを実現させているキー設定が以下の部分なのです。
(“M-<Space>”, sendMessage NextLayout)
タプルの第2要素にはsendMessage関数があり、NextLayoutという「メッセージ」が渡されています。 この関数の結果がレイアウトを切り替えるアクションとなっているのです。
また、別のものを見てみましょう。 マスターペインの大きさを変えるキーバインドを普段使っているでしょうか? デフォルトのTallレイアウトで複数ウィンドウを開いている場合、 左側にマスターペイン、右側にそれ以外のペインが並びます。その状態でMod+lすると、マスターペインが広がり、Mod+hするとマスターペインが狭まります。
(“M-h”, sendMessage Shrink)
(“M-l”, sendMessage Expand)
これを実際に担っているのが、上記のキーバインドの設定です。タプルの第2要素にはsendMessage関数があり、
Shrinkという「メッセージ」が渡されています。「Shrin」は縮むという英語で、これに対となっている「Expand」も見つけることが出来るはずです。
また、sendMessage $ IncMasterN 1
も、同様であり、マスター側のウィンドウを増やしたり減らしたりするメッセージを送ることが出来ます。
(“M-,”, sendMessage $ IncMaserN 1)
(“M-.”, sendMessage $ IncMasterN (-1))
ShrinkやExpandは、Tallだけに効果があるメッセージでは無くて、 Tallに似たマスターペインを持つ他のレイアウトでもその効果が発揮できるようになっています。 これ以外にも、レイアウト毎にそれに独特の効果を与えるメッセージが定義されているものもあるかもしれません。 実際に新しいレイアウトを利用する場合には、クールな機能を見逃さない様に、レイアウトのリファレンスでメッセージについて記載が無いか確認を忘れないようにしましょう。 では、ここでは、Tallのリファレンスを見てみましょう。
http://hackage.haskell.org/package/xmonad-0.15/docs/XMonad-Layout.html
Tallデータ型の説明の部分に以下の記述があります。
The builtin tiling mode of xmonad. Supports Shrink, Expand and IncMasterN.
また、先に紹介したNextLayoutについては、data ChangeLayoutの項目にあり、
実際には、(|||)演算子で結合された複合的なレイアウトであるChoose l r a型のレイアウトの中で、
現実のレイアウトを選択するためのメッセージとして使われるものです。
いつでも一番初めに定義してあるレイアウトに切り変えることが出来る
FirstLayout
というメッセージがあることも、ココを見ると分かります。
ワークスペースとフォーカスの移動
普段使い慣れている、フォーカスを次のウィンドウに移すというキーバインドは、 以下の様に設定されています。
(“M-j”, windows W.focusDown)
タプルの2番目の中身は、windows関数に、W.focusDown
を引数として渡しています。
つまり、xmonadのウィンドウのフォーカス移動は単純なアクションを生み出す1つの関数ではないのです。
このようなワークスペースやウィンドウのフォーカスの移動に関してのアクションの式をスラスラと読めるようになるには、
実は、xmonadに関して知っておかなければならない知識があります。
キーワードとなるのは、まず、「スタックセット」という言葉です。
まず、W.focusDown
の"W.”の部分は、
xmonad.hsの最初の方で定義するimport項目でXMonad.StackSetモジュールのための識別子を短縮するために定義したものです。
ここでまず「スタックセット(StackSet)」という言葉に出会うことが出来ます。
import qualified XMonad.StackSet as W
また、W.focusDown
というのは、XMonad.StackSetモジュールのfocusDown関数という意味になります。
スタックセットとは
さて、普段xmonadを利用していれば、 その画面の状態は「ワークスペース」と「ウィンドウ」で構成されている事を知っていると思います。 xmonadには、1とか2とか3という名前を持っているワークスペースが幾つがあって、 そのうちのどれかが実際のモニタに表示されています。 そして、xmonad上で開いているウィンドウは、どこかのワークスペースに所属していて、 また、現在表示されているワークスペースにあるウィンドウのどれかにフォーカスが当たっています。
実は、これら上記で述べたようなワークスペースとウィンドウの状況を xmonadでは一つのデータとして抽象化して把握しています。 そして、そのデータの事を「スタックセット」と呼んでいます。 下記のモジュールドキュメントを開いてみて下さい。 ページは英語ですが、初めに、スタックセットの概念図が図示されています。 これを眺めることでスタックセットの構造をイメージする手助けになると思います。
https://hackage.haskell.org/package/xmonad-0.15/docs/XMonad-StackSet.html
スタックセットのイメージ図の引用
Workspace { 0*} { 1 } { 2 } { 3 } { 4 }
Windows [1 [] [3* [6*] []
,2*] ,4
,5]
さて、ここで把握しておくべきことは、 スタックセットがどんな構造をしているかの詳細を覚えるとか、 そういう面倒な事ではありません。 先ほどから述べている通り、xmonadでは現在のウィンドウ等の状況が一つの 「データ」として表現されているということを把握しましょう。
スタックセットとフォーカスの移動
ここで少し想像してみて下さい。 ウィンドウのフォーカスを次のウィンドウへ移動させる時、xmonadでは、どんなことが起こっていると思いますか?
まず、xmonadは、もともと、あるウィンドウにフォーカスがある事を表現するデータをもっていますが、 フォーカスを移動させるためには、次のウィンドウがフォーカスを持った状態を表現するデータを作成します。 そして、そのデータに基づいて、xmonadの実際の表示を変更します。 つまり、まず、「次のウィンドウにフォーカスを持った状態を表現するデータ」が、 元の状態のデータを受けて、新しい状態のデータを返す「関数」によって作成されます。 そして、次に、出来上がった新しい状態のデータを基に実際の表示を行うアクションが作られます。
これを踏まえて、ウィンドウのフォーカスを次へ移す実際の式windows W.focusDown
にもう一度着目してみます。
W.focusDown
は、実は関数であり、
ある状態のスタックセットデータを引数として受け取り、
それを基に、次のウィンドウにフォーカスが移った状態のスタックセットデータを返します。
関数に関する型は次のようになっています。
focusDown :: StackSet i l a s sd -> StackSet i l a s sd
以下のリファレンスを参照してみてください。focusDownだけでなく、 focusUpや、focusMaster等、他にもスタックセットを変化させる同じ型を持つ関数が周辺に見られるはずです。
http://hackage.haskell.org/package/xmonad-0.15/docs/XMonad-StackSet.html#g:7
次に、windows
も関数であり、「スタックセットを受け取ってスタックセットを返す関数」を引数にとり、
そして、新しいスタックセットの状態を反映させるアクションを返します。
この関数は、XMonad.Operationsモジュールにあり、関数に関する型を見れば次のようになっています
(引数等についてはWindwSetという型が書かれていますが、これはスタックセットの具体型の別名であり、ここではスタックセットと同じです)。
windows :: (WindowSet -> WindowSet) -> X ()
このように、windows
関数は、(WindowSet -> WindowSet)
のような関数を引数にとるということであり、
そして、「そのような関数」がfocusDown
という事になります。
関数を引数に取る関数のパターン
このように、フォーカス移動やワークスペース移動に関するアクションを返す関数は、その引数に関数を取るものばかりです。 これは、haskellにおいて刻一刻とかわる「現在の外の状態」は、アクションを通じて純粋な環境に呼び込み、 それを純粋な関数で変化させた結果をアクションで外界に反映させるという純粋関数ならではのサイクルを反映しています。
例えば、focusDown関数を単独で使おうとすると、スタックセットを引数に与える必要がありますが、 自分でこれを用意することが出来ません。何らかの方法で、現在のxmonadの状態であるスタックセットを取得する必要があります。 それを担ってくれるのがwindws関数というわけです。 そして、スタックセットを取得する処理と、スタックセットを変化させる処理が分かれていれば、 スタックセットを変化させる処理を入れ替えるだけで、色々な状況変化を作ることが出来るようになるわけです。 このようなパターンが全てというわけではありませんが、 haskellでは、アクションで何かのデータを得て、それを関数で処理するという場合、 それを処理する関数に色んなパターンが使えるように、関数が引数になるので、 このようなパターンに慣れていきましょう。
ワークスペースの切り替え
次に、xmonad.hsでワークスペースの切り替え部分の設定は、 リスト内包表記という書式が使われていて、 haskell初心者にとって複雑に見えるところです。 まずは、実物のコードを見てみましょう。
[(“M-” ++ m ++ show k , windows $ f i)
| (i,k) <- zip (XMonad.workspaces conf) ([1..5] :: [Int])
, (f,m) <- [(W.view, “”),(W.shift, “S-”)]
]
リスト内包表記は、 パターンマッチとフィルター等を使ってある集合から必要な要素だけをリスト化するのに便利な書式で、 特に、自動的に組み合わせパターンを作成してくれるのも便利な点です。 リスト内包表記では、"|(縦棒)"の前と後ろに分けられます。上記の式をよく見ると"|(縦棒)"の前に以下の式があります。
(“M-” ++ m ++ show k , windows $ f i)
これは、ふたつの要素からなるタプルになります。一つ目の要素は“M-” ++ m ++ show k
で、
これは文字列の演算を行っています。mとkはパターンマッチの変数で、この部分が"|(縦棒)"の後ろで定義される集合に一致します。
二つ目の要素はwindows $ f i
です。fとiがパターンマッチの変数で、この部分が"|(縦棒)"の後ろで定義される集合に一致します。
"|(縦棒)"の後ろは、カンマで区切られていて、複数の事が書かれています。まずは、その一つ目の内容を見ましょう。
(i,k) <- zip (XMonad.workspaces conf) ([1..5] :: [Int])
<-
の前に、タプル(i,k)があり、これがパターンマッチ変数を表しています。
<-
の後ろに、集合全体の定義がされています。zip関数は、二つのリストを引数として取り、これをペアのタプルとして返します。
zip関数一つ目の引数はXMonad.workspaces conf
は、["1","2","3","4","5"]
です。
そして、2つ目の引数([1..5]::[Int])
は、[1,2,3,4,5]
です。ですから、zip関数の結果は、
[("1",1), ("2",2), ("3",3), ("4",4), ("5",5), ]
となります。
でわ、"|(縦棒)"の二つ目の内容を見ましょう。
(f,m) <- [(W.view, “”),(W.shift, “S-”)]
<-
の前に、タプル(f,m)があり、これがパターンマッチ変数を表しています。
<-
の後ろに、集合全体の定義は、関数と文字列がペアになった二つのタプルが具体的に書かれています。
このように、"|(縦棒)"の後ろで定義された集合と、それに対するパターンマッチが、"|(縦棒)"の前の部分の形で定義されます。 そのさい、複数の集合がある場合、それらの組み合わせ全てが"|(縦棒)"の前の部分の形で定義されることになります。
これ以上の「リスト内包表記」自体の説明は、別のページに譲るとして、 ここでは、リスト内包表記の書式が展開された後の形について、 ワークスペースの切り替えを行う関数について着目していきます。 そこで、リスト内包表記を理解して読み解けば、以下のようようになります。
[(“M-1”, windows $ W.view “1”)
,(“M-S-1”, windows $ W.shift “1”)
,(“M-2”, windows $ W.view “2”)
,(“M-S-2”, windows $ W.shift “2”)
…省略
]
ここでの設定は、シフトが無い場合は、数字に対応するワークスペースへ移動し、
シフトがある場合は、フォーカスのあるウィンドウをその数字に対応するワークスペースへ移動させるものです。
タプルの第一要素であるキーの組み合わせについては、「Mod + 数字」若しくは「Mod + Shift + 数字」です。
そして、タプルの第二要素であるアクションには、windows関数にスタックセットモジュールのshift
関数と、
view
関数が見られます。この両関数は、"1"や"2"という数字の文字列を引数に取っていますが、
これは、ワークスペース名を表しています。
以下の、XMonad.StackSetモジュールのドキュメントを参照してください。
http://hackage.haskell.org/package/xmonad-0.15/docs/XMonad-StackSet.html#g:5
まずは、view関数の説明があります。型は次のようになっています。 一つ目の引数であるiの部分に、ワークスペース名を渡してやると、関数の部分適用で、残りはスタックセットを受け取ってスタックセットを返す関数となります。
view :: (Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd
次は、shift関数を見てみましょう。
http://hackage.haskell.org/package/xmonad-0.15/docs/XMonad-StackSet.html#g:10
shift関数の型は次のようになっています。 viewの時と同じように、一つ目の引数であるiの部分に、ワークスペース名を渡してやると、 関数の部分適用で、残りはスタックセットを受け取ってスタックセットを返す関数となります。
shift :: (Ord a, Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd
このように、view関数もshift関数もワークスペース名のみを渡した関数の部分適用により、 「スタックセットをもらってスタックセットを返す関数」になります。 そして、この関数をwindows関数に渡してアクションを作っているのです。
リストの接続
さて、このリスト内包表記と、それ以前のリストが"++“演算子で繋がれていることに着目しておきましょう。 キーバインドに関するリストを動的に作って複雑になる場合などは、別の場所で定義して、その定義されたものをここに”++"演算子で繋げるという方法を使えば、 コードが見やすくなることもあります。
0 件のコメント:
コメントを投稿