XMonadのLayoutからWindowのプロパティにアクセスする
Tl;Dr
LayoutClass
のインスタンスの定義で、a
をWindow
に固定するどこかに影響あるかもしれない、未確認
doLayout
の中でQuery
を使ってあげる
モチベーション
ダッシュボードとして機能するworkspaceを作成したい。
なので所定の位置に各アプリケーションが配置されてほしいが、
何もしない状態だと各アプリケーションのタイトル(WM_NAME
)等には
アクセスできず判断できない。
なんとかならないものか
ManageHookを参考にする
managehookでは、ウィンドウのプロパティにアクセスして制御を行っている。
title "This is example emacs" --> doShift "Editor"
など。 ここにヒントを得れば何らかの方法がありそうだなと検討が付く
Query
を探る
managehookで使われているのは Query
という型。これは X
を基底
モナドに持つ ReaderT
で、 runQuery
で実行することで X a
を
戻り値として取ることができる。
newtype Query a = Query (ReaderT Window X a)
deriving (Functor, Applicative, Monad, MonadReader Window, MonadIO)
runQuery :: Query a -> Window -> X a
runQuery (Query m) w = runReaderT m w
さて...どうやらこれで答えなのでは?
X
モナドのアクションなので、あとはこれを doLayout
内部から呼んであげれば良さそう。
...が、人生そう甘はないのである
Query
では上手くいかない!?
改めて runQuery
の型を見てみよう。
runQuery :: Query a -> Window -> X a
第二引数に取るのは Window
である。
~doLayout~の型も見てみよう
doLayout :: layout a - Rectangle -> Stack a -> X ([(a, Rectangle)], Maybe (layout a))
このうち、ウィンドウの情報が格納されているのは Stack a
の内部である。(コメントより)
そして Stack a
の定義 を見てみると
data Stack a = Stack { focus :: !a -- focused thing in this set
, up :: [a] -- clowns to the left
, down :: [a] } -- jokers to the right
deriving (Show, Read, Eq)
となっており、 保存されている値の型は a
で定義されている のだ。
これが何故問題になるか、というと runQuery
に Stack a
の中の Window
を食わせたいが、
型が定まっていないため型エラーを起こす ということだ。
ドキュメント的に、 doLayout
には実際には Stack Window
が渡されるであろうことは明白なのだが、
プログラムからするとそんな実行時の話は知らない。
というか型としてはエラーを起こすのが正解だと思う。
でも確実に Window
がくるはずだし、困った...
LayoutClass <Layout> Window
にしちゃえ!!
えいやっ!というやっつけ感が凄いが、 LayoutClass
のインスタンスを作る際に a
の型を指定してしまえば
型エラーは起こらなくなる。 doLayout
は LayoutClass
の型クラス関数であり、 doLayout
の型定義の中の
a
は LayoutClass <Layout> a
によって束縛されているからだ。
これによって、 Stack a
の型が固定される。
instance LayoutClass MyLayout Window where
doLayout :: MyLayout Window -> Rectangle -> Stack Window -> X ([(Window, Rectangle)], Maybe (MyLayout Window))
そうすれば、後は Stack
から取り出した Window
を runQuery
にかけてあげれば良いだけになる。
おまけ: 具体的な書き方
ついでなので、ケース毎に使い方(書き方)を書いてみる。
タイトルによって選択
title
を使います。
私のxmonadに記載されているものと同じです。
最初に [Window]
から 適切な Window
だけ取り出すために [X (Maybe Window)]
にしてあげて、 X
を実行するために sequence
(X [Maybe Window]
)、
find
で Just
なもの(=求めていたWindow)をピックアップしてあげて
(X [Maybe (Maybe Window)]
)、二重になった Maybe
を join
で平たくしてあげています。
windowTitleIs :: String -> [Window] -> X (Maybe Window)
windowTitleIs name ws = fmap (join . find isJust) . sequence . flip fmap ws $ \w -> flip runQuery w $ do
n <- w`hasTitle`name
if n then return (Just w) else return Nothing
hasTitle :: Window -> String -> Query Bool
hasTitle w name = (== name) <$> title
ついで(?)なので思考メモを残しておきます。
allWindows :: [Window]
fmap :: Functor f => (a -> b) -> f a -> f b
flip :: (a -> b -> c) -> b -> a -> c
flip fmap :: Functor f => f a -> (a -> b) -> f b
flip fmap allWindows :: (Window -> b) -> [b]
flip fmap allWindows <$> :: Functor f => f ([Window] -> (Window -> b)) -> f [b]
f :: Window -> X (Maybe Window)
flip fmap allWindows f :: [X (Maybe Window)]
sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)
sequence $ flip fmap allwindows f :: X [Maybe Window]
find :: Foldable t => (a -> Bool) -> t a -> Maybe a
(<$>) :: Functor f => (a -> b) -> f a -> f b
find isJust :: Foldable t => t a -> Maybe a
find isJust <$> :: Foldable t, Functor f => f (t a) -> f (Maybe a)
find jsJust <$> sequence $ flip fmap allwindows f :: X (Maybe (Maybe a))
join :: Monad m => m (m a) -> m a
fmap join :: (Monad m, Functor f) => f (m (m a)) -> f (m a)
($) :: (a -> b) -> a -> b
fmap join $ :: (Monad m, Functor f) => f (m (m a)) -> f (m a)
fmap join $ find jsJust <$> sequence $ flip fmap allwindows f :: X (Maybe a)