[Xmonad] More user-friendly hook system

Joachim Breitner mail at joachim-breitner.de
Sun Oct 7 05:46:17 EDT 2007


Hi,

I’ve been given some thought on the way we make the user install the
hooks from extensions. Currently, we have two hooks (manageHook and
logHook), with a different logic for combining hooks:

For manageHook, we pass the window some hopefully useful data as
paramenters (name, class) and exepect to get a X (WindowSet ->
WindowSet) back. The user can use guards to select which hook to run.

Advantage:
 - relatively easy for the user to add simple custom hooks
Disadvantage:
 - hard to add hooks that need to do X stuff to find out if they have to
   run (e.g. by getting other Properties of the window).
 - hard to install hooks so that more than one can run for a window.

For loogHook, we just require the user to return an X () action.

Advantage:
 - flexible
Disadvantage:
 - different method than manageHook
 - user needs to know Monad syntax

Additionally, it is hard to add more hook (init hook, hooks on
ClientMessage events that would be needed for EWMH interaction), and the
user is confused by different hook mechanism.

I propose therefore this system:

User interface
==============
For the user, it should be very simple to add extensions that need
hooks. Therefore, there should just be a simple XMonadHook type (where
the implementation should not matter to the user), and a single function
in the config:

> hooks :: [XMonadHook]
> hooks = [ewmhHook
>	 , loatGimpHook
>	 , someHookWithAString "text"
>	 , someHookWithConfig someConfig
>	 ]
>
> someConfig = -- more complicated per-hook information

Functionality such as matching a window name on manage (as provided by
the current system) should be make possible through an extension on it’s
own: See the PropManage extension, which already works like this.

Possible implementation
=======================
Note that given this user interface, there might other ways to implement
this. This is just my idea.

Assume we want to support four hooks: initHook, manageHook,
clientMessageHook and logHook. We also don’t want to change code in
Extensions when new hooks are added.

I’d give types to the single hooks:

> type InitHook :: X ()
> type MangageHook :: Window -> X (WindowSet -> WindowSet)
> type ClientMessageHook :: ClientMessage -> X ()
> type logHook :: X()

for every hook, we have a “no nothing” default hook:

> defaultInitHook :: InitHook
> defaultInitHook = return ()
> defaultManageHook :: ManageHook
> defaultManageHook _ = return (id)
> defaultClientMessageHook :: ClientMessageHook
> defaultClientMessageHook _ = return ()
> defaultLogHook :: LogHook
> defaultLogHook = return ()

The XMonadHook is then a record of possible hooks, and we bunde the
default hooks:

> type XMonadHook = XMonadHook {
>			 initHook :: InitHook
>			,manageHook :: MangeHook
>			,clientMessageHock :: ClientMessageHook
>			,logHook :: LogHook
>			}
> defaultXMonadHook :: XMonadHook
> defaultXMonadHook = XMonadHook defaultInitHook defaultManageHook defaultClientMessageHook defaultLogHook

To run the hooks given by the user, we map the appropriate accessor over
config list and fold the types together, depending on the type

> runInitHooks :: [XMonadHook] -> X ()
> runInitHooks = foldl (>>) (return ()) . map initHook
> runManageHooks :: [XMonadHook] -> Window -> X (WindowSet -> WindowSet)
> runManageHooks hooks window = foldl (.) (id) `fmap` mapM (($window) . manageHook) hooks
> runClientMessageHooks :: [XMonadHook] -> ClientMessage -> X ()
> runClientMessageHooks hooks cm = foldl (>>) (return ()) . map (($ cm) . initHook)
> runLogHooks :: [XMonadHook] -> X ()
> runLogHooks = foldl (>>) (return ()) . map logHook

Extensions then can write their own hook(s) and override the appropriate
fields of defaultXMonadHook:

> module StupidLogger where
>
> myLogHook :: LogHook
> myLogHook = io $ print "State changed"
>
> stupidLoggerHook :: XMonadHook
> stupidLoggerHook = defaultXMonadHook { logHook = myLogHook }

As you can see, adding new hooks would not break this module. Hooks that
need some configuration (e.g. PropManage the list things to match), you
just require a parameter for your hook

> module NameMatcher where
>
> type NameMatcherConf = [String, X()]
>
> myManageHook :: NameMatcherConf -> ManageHook
> myManageHook = ...
>
> nameMatcherHook :: NameMatcherConf -> XMonadHook
> nameMatcherHook conf = defaultXMonadHook { manageHook = myManageHook conf }


That’s it so far. Note that this code is untested. If you think this
would be a viable user interface or even a reasonable implementation,
I’d be willing to implement that today.

Greetings,
Joachim


-- 
Joachim Breitner
  e-Mail: mail at joachim-breitner.de
  Homepage: http://www.joachim-breitner.de
  ICQ#: 74513189


More information about the Xmonad mailing list