[xmonad] adventures in types

Andrea Rossato mailing_list at istitutocolli.org
Mon Jan 28 04:46:59 EST 2008


Hi,

this is a brief description of the stuff I've been lately polluting
the mailing list with: the new decoration framework, this floating
layout and so on. I'm going to be long. Sorry, but I though that such
a digression could be useful for non/wannabe developers too.

So, it all started with "doLayout":
doLayout :: layout a -> Rectangle -> Stack a -> X ([(a, Rectangle)], Maybe (layout a))

take the screen geometry, an ordered stack of windows and produce a
list of those widows coupled with their generated geometry and,
possibly, an updated version of the layout - the way of doing the same
operation the *next* time.

Cool. This is what a window manager is all about, after all... But
soon we discovered that manipulating a list of windows and their
geometry, when you have the screen geometry and the ordered stack, may
be more fun. You have more information, after all.

This is how layout modifier started to appear. We stared seen layouts
doing:

(wrs,ml) <- doLayout l r s

and then you start playing with "wrs"...;)

When we switched to the class approach, David Roundy (my mentor when
it comes to type classes and stuff like that, even though I have the
feeling this has been a type adventure for him too...[1]), came up
with a nice solution: a layout (ModifiedLayout) which could take a
layout modifier and produce a ... modified layout!

The layout modifier must just be an instance of the LayoutModifier
class, with safe default method implementations. The basic method is
"redoLayout":

redoLayout :: m a -> Rectangle -> Stack a -> [(a, Rectangle)] -> X ([(a, Rectangle)], Maybe (m a))

As you see, this is like a "doLayout", but it gets the already
produced list of windows and their respective geometry. You have the
whole workspace populated of windows, and you can manipulate them!

So people started writing layout modifiers with this new framework.
But sometimes you can still see layout modifiers that are implemented
as layouts, that is to say, as member of the LayoutClass: Mirror,
Reflect, AvoidStruts just to mention a few. Why?

In most of the cases this happens just because of the need to modify
the rectangle or the stack to be passed to the "doLayout" with the
layout to be modified. Unfortunately the LayoutModifier class doesn't
(well, didn't) have something to deal with this situation.

So I created a new method class, "modifyLayout":

modifyLayout :: (LayoutClass l a) => m a -> l a -> Rectangle -> Stack a -> X ([(a, Rectangle)], Maybe (l a))

Now you can access the layout you are modifying and you can change the
rectangle and the stack. Then you have the generated list of windows
and geometries to start playing with. And, for free, you get all the
safe methods for correctly passing messages around, for dealing with
empty workspaces, and so on.

Now we can rewrite Reflect as a layout modifier:

Sun Jan 27 17:58:54 CET 2008  Andrea Rossato <andrea.rossato at unibz.it>
  * Reflect: reimplemented as a layout modifier (which makes it compatible with windowArranger and decoration)
    M ./XMonad/Layout/Reflect.hs -15 +11

The same with AvoidStruts:
Sun Jan 27 15:43:01 CET 2008  Andrea Rossato <andrea.rossato at unibz.it>
  * ManageDocks: implement AvoidStruts as a layout modifier
    M ./XMonad/Hooks/ManageDocks.hs -17 +11

You see? The code gets shorter! And the source of many bugs gets
eliminated!

I've also add the pure versions of the redoLayout and the message
handling methods: this way you can start manipulating the list of
(windows,geometries) purely... Don, do you like that possibility??

I'm sure you do!
;-)

Ok, so, what about this New Decoration Framework I've been talking
about?

After this small type class cleanup (well it did not occur in this
order actually, it's an adventure, after all...;), I started thinking
about a floating layout.

My first intent was to have a way of manipulating the windows'
geometry with the keyboard. This is how WindowArranger started.

The idea is to have a layer, a layout modifier, with a list of windows
whose geometry is managed by the underlying layout. But when a
window's geometry is modified by a message sent by the user, its
geometry will not be managed by that layout anymore, but only by the
WindowArranger modifier itself.

This is done with the ArrangedWindow type, with 2 constructors 
"WR (a,Rectangle)"
for the windows managed by the underlying layout and
"AWR (a,Rectangle)"
for windows whose geometry has been modified by the user.

This list is processed with pure code only! And WindowArranger was the
fist pure layout modifier written.

I then decided to rewrite Tabbed too. Once someone asked to have tabs
with other layouts too. This is how I got the idea of transforming the
Tabbed layout in a Tabbed layout modifier.

Decorating a window only means creating a second window with a given
geometry and inserting it in the window list before the window we are
decorating. We need to keep track of all that, as we did with tabbed,
but that can be done with pure code only, as the WindowArranger
demonstrated. So I rewrote tabbed in a more general way, as an
instance of the LayoutModifier, and to be used with ModifiedLayout. I
didn't need to write anything to communicate with the X server. I just
used XMonad.Util.XUtils. I just needed to write the pure core to
process the state for keeping track of the decorations we created, and
the pure code for inserting them in the (window,geometry) list to be
passed to xmonad for actually refreshing the screen.

And I obviously copied my mentor's (David Roundy) approach. I created
a class, DecorationStyle, which basic method is "decorate":

decorate :: ds a -> Dimension -> Dimension -> Rectangle -> W.Stack a -> [(a,Rectangle)] -> (a,Rectangle) -> X (Maybe Rectangle)

a decoration style, the user configured width and height of the
decoration, the screen and the stack, the windows with their geometry,
and the specific window (and its geometry) you are creating the
decoration for. If you return Just a geometry, that geometry will be
used to create the decoration (with the user supplied configuration),
otherwise no decoration is created for that window.

Obviously there are pure methods here too.

This is XMonad.Layout.Decoration: a layout modifier that takes a
member of the DecorationStyle class and a shrinker to generate the
geometry of a decoration to be passed to xmonad..

So I then created some decoration styles: the default one
DefaultStyle, a tabbed like decoration (XMonad.Layout.Tabbed), a very
basic SimpleDecoration (Xmonad.LayoutSimpleDecoration), and what I was
told was the old dwm like decoration style (XMonad.Layou.DwmStyle).

Together with these decoration styles there are some simple
layoutModifier to apply those decoration to any layout. Remember that
Decoration is an instance of the LayoutModifier class:

- a simpleDeco modifier:
simpleDeco :: Shrinker s => s -> DeConfig SimpleDecoration a -> l a -> ModifiedLayout (Decoration SimpleDecoration s) l a

- a dwmStyle modifier:
dwmStyle :: (Eq a, Shrinker s) => s -> DeConfig DwmStyle a -> l a -> ModifiedLayout (Decoration DwmStyle s) l a

All these modifiers take a shrinker (the Shrinker class was one of
David's classes), and default configuration.

Then I rewritten the tabbed layout, as a layout modifier of the
XMonad.Layout.Simplest layout, a pure one line layout I wrote
specifically for tabbed:
 
- a tabbed layout:
tabDeco :: (Eq a, Shrinker s) => s -> DeConfig TabbedDecoration a -> ModifiedLayout (Decoration TabbedDecoration s) Simplest  a

(there's also a compatibility layer with the old tabbed layout)

Then I added some other tools for helping combining this stuff, such
as ResizeSize, a layout modifier to modify the screen geometry.

That's it. Suppose you want to apply the tabbed decoration to circle
without shrinking each window:
decoration shrinkText defTabbedConfig (resizeVertical 20 Circle)

Do you want to add a simple decoration to all your layouts, with the
possibility of moving/resizing windows?
simpleDeco shrinkText defaultSimpleConfig (windowArrange $ layoutHook defaultConfig)

Obviously we can hide this combinations to the users, and provide them
with the final stuff.

Ok, so, where is the floating layout?

Well, it is just a side product of this stuff. Go read it:
http://gorgias.mine.nu/repos/xmonad-contrib/XMonad/Layout/SimpleFloat.hs

This is its type:
simpleFloat :: ModifiedLayout (Decoration SimpleDecoration DefaultShrinker) (ModifiedLayout WindowArranger SimpleFloat) a

SimpleFloat is a very simple layout (of the LayoutClass) which will
look at the newly created window's attributes and set its geometry
accordingly. The WindowArranger is set so that it will arrange all
windows after creation (which means the underlying layout will not
touch their geometry anymore). The WindowArranger lets you to
move/resize them. On top of that I added the a Decoration modifier
with a SimpleDecoration style and the default shrinker.

simpleFloat' will let you provide a custom configuration and a custom
shrinker.

To support this stuff the only required change to the core (Spencer
pushed it yesterday) is to add "emptyLayout" to the LayoutClass, so
that a layout is called even when the workspace is empty.

This has many beneficial effects. ShowWName, for instance, can show
the names of empty layouts too.

The mouse UI should go on to of Decoration. But there's one side
affect: a layout modifier *cannot* call "focus w". I still don't
exactly understand the whys and hows, but I'm sure it cannot.

I'm ready to push. My code is backward compatible. But there are those
layout modifier implemented as instances of the LayoutClass. If used
in the chain of layout modifiers I showed you above, if they do not
implement the "emptyLayout" method correctly they will prevent
Decoration from removing the last windows.

I'll wait some feedback before pushing.

Sorry I was so long, but I wrote quite some code.

Cheers,
Andrea

[1] Read here :) http://www.haskell.org/pipermail/xmonad/2007-September/002148.html


More information about the xmonad mailing list