Xmonad/Guided tour of the xmonad source
From HaskellWiki
m |
|||
| Line 36: | Line 36: | ||
Without further ado, let's begin! | Without further ado, let's begin! | ||
| - | + | [[/StackSet.hs]] | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
== Core.hs == | == Core.hs == | ||
| Line 304: | Line 108: | ||
Next, let's take a look at the <hask>LayoutClass</hask>. This is one of the places that Haskell's type classes really shine. <hask>LayoutClass</hask> is a type class of which every layout must be an instance. It defines the basic functions which define what a layout is and how it behaves. The comments in the source code explain very clearly what each of these functions is supposed to do, but here are some highlights: | Next, let's take a look at the <hask>LayoutClass</hask>. This is one of the places that Haskell's type classes really shine. <hask>LayoutClass</hask> is a type class of which every layout must be an instance. It defines the basic functions which define what a layout is and how it behaves. The comments in the source code explain very clearly what each of these functions is supposed to do, but here are some highlights: | ||
| - | * Note that all the <hask>LayoutClass</hask> functions provide default implementations, so that <hask>LayoutClass</hask> instances do not have to provide implementations of those functions where the default behavior is desired. For example, by default, <hask>doLayout</hask> simply calls <hask>pureLayout</hask> | + | * Note that all the <hask>LayoutClass</hask> functions provide default implementations, so that <hask>LayoutClass</hask> instances do not have to provide implementations of those functions where the default behavior is desired. For example, by default, <hask>doLayout</hask> simply calls <hask>pureLayout</hask>, so a layout that does not require access to the X monad need only implement the <hask>pureLayout</hask> function. (For example, the Accordion, Square, and Grid layouts from the contrib library all use this approach.) |
* Layouts can have their own private state, by storing this state in the <hask>LayoutClass</hask> instance and returning a modified structue (via <hask>doLayout</hask>) when the state changes. | * Layouts can have their own private state, by storing this state in the <hask>LayoutClass</hask> instance and returning a modified structue (via <hask>doLayout</hask>) when the state changes. | ||
| Line 323: | Line 127: | ||
=== Messages === | === Messages === | ||
| + | The xmonad core uses <i>messages</i> to communicate with layouts. The obvious, simple way to define messages would be by defining a new data type, something like | ||
| + | |||
| + | <haskell> | ||
| + | -- WARNING: not real xmonad code! | ||
| + | |||
| + | data Message = Hide | ReleaseResources | ShowMonkey | ||
| + | </haskell> | ||
| + | |||
| + | thus defining three messages types, which tell a layout to hide itself, release any resources, and display a monkey, respectively. | ||
| + | |||
| + | The problem with such an approach should be obvious: it is completely inflexible and inextensible; adding new message types later would be a pain, and it would be practically impossible for extension layouts to define their own message types without modifying the core. So, xmonad uses a more sophisticated system (at the cost of making things slightly harder to read and understand). | ||
| + | |||
| + | Instead of defining a <hask>Message</hask> data type, xmonad defines a <hask>Message</hask> <i>type class</i>: | ||
| + | |||
| + | <haskell> | ||
| + | class Typeable a => Message a | ||
| + | |||
| + | data SomeMessage = forall a. Message a => SomeMessage a | ||
| + | </haskell> | ||
| + | |||
| + | The <hask>Message</hask> class comes along with an existential wrapper <hask>SomeMessage</hask>, just like <hask>LayoutClass</hask> comes along with the <hask>Layout</hask> wrapper. | ||
| + | |||
| + | Note also that the <hask>Message</hask> type class doesn't declare any methods; it simply serves as a marker for types whose values we wish to use as messages. The <hask>Typeable</hask> constraint ensures that the types of values used as messages can be carried around as values at runtime, so dynamic type checks can be performed on messages extracted from a <hask>SomeMessage</hask> wrapper: | ||
| + | |||
| + | <haskell> | ||
| + | -- | And now, unwrap a given, unknown Message type, performing a (dynamic) | ||
| + | -- type check on the result. | ||
| + | -- | ||
| + | fromMessage :: Message m => SomeMessage -> Maybe m | ||
| + | fromMessage (SomeMessage m) = cast m | ||
| + | </haskell> | ||
| + | |||
| + | Complicated? A bit, perhaps, but the good news is that you probably don't have to worry too much about it. =) | ||
| + | |||
| + | Finally, raw X events (like key presses, mouse movements, and so on) count as <hask>Message</hask>s, and two core message values are also defined as members of the <hask>LayoutMessages</hask> type. | ||
| + | |||
| + | <haskell> | ||
| + | -- | X Events are valid Messages | ||
| + | instance Message Event | ||
| + | |||
| + | -- | LayoutMessages are core messages that all layouts (especially stateful | ||
| + | -- layouts) should consider handling. | ||
| + | data LayoutMessages = Hide -- ^ sent when a layout becomes non-visible | ||
| + | | ReleaseResources -- ^ sent when xmonad is exiting or restarting | ||
| + | deriving (Typeable, Eq) | ||
| + | |||
| + | instance Message LayoutMessages | ||
| + | </haskell> | ||
=== Utility functions === | === Utility functions === | ||
=== On-the-fly recompilation === | === On-the-fly recompilation === | ||
Revision as of 20:12, 4 March 2008
Contents |
1 Introduction
Do you know a little Haskell and want to see how it can profitably be applied in a real-world situation? Would you like to quickly get up to speed on the xmonad source code so you can contribute modules and patches? Do you aspire to be as cool of a hacker as the xmonad authors? If so, this might be for you. Specifically, this document aims to:
- Provide a readable overview of the xmonad source code for Haskell non-experts interested in contributing extensions or modifications to xmonad, or who are just curious.
- Highlight some of the uniquenesses of xmonad and the things that make functional languages in general, and Haskell in particular, so ideally suited to this domain.
This is not a Haskell tutorial. I assume that you already know some basic Haskell: defining functions and data; the type system; standard functions, types, and type classes from the Standard Prelude; and at least a basic familiarity with monads. With that said, however, I do take frequent detours to highlight and explain more advanced topics and features of Haskell as they arise.
2 First things first
You'll want to have your own version of the xmonad source code to refer to as you read through the guided tour. In particular, you'll want the latest darcs version, which you can easily download by issuing the command:
darcs get http://code.haskell.org/xmonad
I intend for this guided tour to keep abreast of the latest darcs changes; if you see something which is out of sync, report it on the xmonad mailing list, or -- even better -- fix it!
You may also want to refer to the Haddock-generated documentation (it's all in the source code, of course, but may be nicer to read this way). You can build the documentation by going into the root of the xmonad source directory, and issuing the command:
runhaskell Setup haddock
which will generate HTML documentation in dist/doc/html/xmonad/.
Without further ado, let's begin!
3 Core.hs
The next source file to examine is Core.hs. It defines several core data types and some of the core functionality of xmonad. If StackSet.hs is the heart of xmonad, Core.hs is its guts.
3.1 XState, XConf, and XConfig
These three record types make up the core of xmonad's state and configuration:
- A value of type stores xmonad's mutable runtime state, consisting of the list of workspaces, a set of mapped windows, something to do with keeping track of pending UnmapEvents, and something to do with dragging.XState
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 ())) }
type WindowSet = StackSet WorkspaceId (Layout Window) Window ScreenId ScreenDetail type WindowSpace = Workspace WorkspaceId (Layout Window) Window -- | Virtual workspace indicies type WorkspaceId = String -- | Physical screen indicies newtype ScreenId = S Int deriving (Eq,Ord,Show,Read,Enum,Num,Integral,Real) -- | The 'Rectangle' with screen dimensions and the list of gaps data ScreenDetail = SD { screenRect :: !Rectangle , statusGap :: !(Int,Int,Int,Int) -- ^ width of status bar on the screen } deriving (Eq,Show, Read)
newtype ScreenId = S Int deriving (...)
- An record stores xmonad's (immutable) configuration data, such as window border colors, the keymap, information about the X11 display and root window, and other user-specified configuration information. The reason this record is separated fromXConfis that, as we'll see later, xmonad's code provides a static guarantee that the data stored in this record is truly read-only, and cannot be changed while xmonad is running.XState
- provides a way for the user to customize xmonad's configuration, by defining anXConfigrecord in theirXConfigfile. You're probably already familiar with this record type.xmonad.hs
3.2 The X monad
And now, what you've all been waiting for: the X monad!
newtype X a = X (ReaderT XConf (StateT XState IO) a) deriving (Functor, Monad, MonadIO, MonadState XState, MonadReader XConf)
For more information on monad transformers in general, I recommend reading Martin Grabmüller's excellent tutorial paper, Monad Transformers Step-by-Step; for more information on this particular style of composing monad transformers and using automatic newtype deriving, read Cale Gibbard's tutorial, How To Use Monad Transformers.
Along with the X monad, several utility functions are provided, such as 3.3 ManageHook
This is a bit more advanced, and not really a central part of the system, so I'm skipping it for now, hopefully coming back to add more later.
3.4 LayoutClass
Next, let's take a look at the - Note that all the functions provide default implementations, so thatLayoutClassinstances do not have to provide implementations of those functions where the default behavior is desired. For example, by default,LayoutClasssimply callsdoLayout, so a layout that does not require access to the X monad need only implement thepureLayoutfunction. (For example, the Accordion, Square, and Grid layouts from the contrib library all use this approach.)pureLayout
- Layouts can have their own private state, by storing this state in the instance and returning a modified structue (viaLayoutClass) when the state changes.doLayout
- Both anddoLayouthave corresponding "pure" versions, which do not give results in the X monad. These functions are never called directly by the xmonad core, which only callshandleMessageanddoLayout, but a layout may choose to implement one (or both) of these "pure" functions, which will be called by the default implementation of the "impure" versions. Layouts which implementhandleMessageorpureLayoutare guaranteed to only make decisions about layout or messages (respectively) based on the internal layout state, and not on the state of the system or the window manager in general.pureMessage
-- | An existential type that can hold any object that is in Read and LayoutClass. data Layout a = forall l. (LayoutClass l a, Read (l a)) => Layout (l a)
3.5 Messages
The xmonad core uses messages to communicate with layouts. The obvious, simple way to define messages would be by defining a new data type, something like
-- WARNING: not real xmonad code! data Message = Hide | ReleaseResources | ShowMonkey
thus defining three messages types, which tell a layout to hide itself, release any resources, and display a monkey, respectively.
The problem with such an approach should be obvious: it is completely inflexible and inextensible; adding new message types later would be a pain, and it would be practically impossible for extension layouts to define their own message types without modifying the core. So, xmonad uses a more sophisticated system (at the cost of making things slightly harder to read and understand).
Instead of defining aclass Typeable a => Message a data SomeMessage = forall a. Message a => SomeMessage a
-- | And now, unwrap a given, unknown Message type, performing a (dynamic) -- type check on the result. -- fromMessage :: Message m => SomeMessage -> Maybe m fromMessage (SomeMessage m) = cast m
Complicated? A bit, perhaps, but the good news is that you probably don't have to worry too much about it. =)
Finally, raw X events (like key presses, mouse movements, and so on) count as-- | X Events are valid Messages instance Message Event -- | LayoutMessages are core messages that all layouts (especially stateful -- layouts) should consider handling. data LayoutMessages = Hide -- ^ sent when a layout becomes non-visible | ReleaseResources -- ^ sent when xmonad is exiting or restarting deriving (Typeable, Eq) instance Message LayoutMessages
