This is the note I take for reading the source code of XMonad. There are really a lot of things I can learn! Additionally I want to write a xmonad window layout.
First we come to the core definitions. The X
monad is a reader state monad over IO
,
newtype X a = X (ReaderT XConf (StateT XState IO) a)
deriving (Functor, Applicative, Monad, MonadFail, MonadIO, MonadState XState, MonadReader XConf)
deriving (Semigroup, Monoid) via Ap X a
this means you can read config and alter state, execute IO
in the X
monad.
I knew what readerT and stateT are, but I did not realize combining them is so easy in Haskell. Here I learned something about deriving instances for newtype. First enable the GeneralizedNewtypeDeriving
extension, if you have already implemented the instances
instance Monad m => Monad (ReaderT r m)
instance Monad m => Monad (StateT s m)
you can automatically derive the instances for the composed monad, by simply writing deriving (Monad, MonadReader r, MonadState s)
. I didn’t knew that and I used to write a custom monad transformer that does reader and state at the same time, it turns out you can do it very easily.
The funny Ap
thing is a newtype wrapper that allows you to derive Monoid
instance for an applicative functor.
For example, if you have a monoid m
and you want to make Maybe m
a monoid, you can use Ap Maybe m
to derive the monoid instance, it will be derived applicatively, like this
Ap m1 <> Ap m2 = Ap ((<>) <$> m1 <*> m2)
this state manages all the window related information, including the workspace list, mapped windows, etc.
-- | XState, the (mutable) window manager state.
data XState = XState
windowset :: !WindowSet -- ^ workspace list
{ mapped :: !(S.Set Window) -- ^ the Set of mapped windows
, waitingUnmap :: !(M.Map Window Int) -- ^ the number of expected UnmapEvents
, dragging :: !(Maybe (Position -> Position -> X (), X ()))
, numberlockMask :: !KeyMask -- ^ The numlock modifier
, extensibleState :: !(M.Map String (Either String StateExtension))
,-- ^ stores custom state information.
--
-- The module "XMonad.Util.ExtensibleState" in xmonad-contrib
-- provides additional information and a simple interface for using this.
}
-- | XState, the (mutable) window manager state.
data XState = XState
windowset :: !WindowSet -- ^ workspace list
{ mapped :: !(S.Set Window) -- ^ the Set of mapped windows
, waitingUnmap :: !(M.Map Window Int) -- ^ the number of expected UnmapEvents
, dragging :: !(Maybe (Position -> Position -> X (), X ()))
, numberlockMask :: !KeyMask -- ^ The numlock modifier
, extensibleState :: !(M.Map String (Either String StateExtension))
,-- ^ stores custom state information.
--
-- The module "XMonad.Util.ExtensibleState" in xmonad-contrib
-- provides additional information and a simple interface for using this.
}
type WindowSet = StackSet WorkspaceId (Layout Window) Window ScreenId ScreenDetail
type WindowSpace = Workspace WorkspaceId (Layout Window) Window
-- | Virtual workspace indices
type WorkspaceId = String
The Layout
wraps any layout types that implements certain classes. This allow us to contain different type classes in one container and enforce the use of interfaces provided by the classes only.
data Layout a = forall l. (LayoutClass l a, Read (l a)) => Layout (l a)
It is the way we hold different types and enforce abstractions in Haskell.
There can be different types of messages that we need to broadcast, and we also need to support user defined messages. The SomeMessage
type is a wrapper that can hold any type of message.
data SomeMessage = forall a. Message a => SomeMessage a
class Typeable a => Message a
where we require the type being runtime typeable, so that we can cast it back to the original type.
fromMessage :: Message m => SomeMessage -> Maybe m
SomeMessage m) = cast m fromMessage (
here cast :: (Typeable a, Typeable b) => a -> Maybe b
is a function that cast a type to another type if they are of the same type.
The most important functions are
runLayout
handleMessage
description