[Xmonad] More user-friendly hook system

Spencer Janssen sjanssen at cse.unl.edu
Wed Oct 10 11:48:52 EDT 2007


On Sunday 07 October 2007 04:46:17 Joachim Breitner wrote:
> 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

I think this would be very useful for contrib modules, put perhaps less easy
to use for regular users.  I'm going to delay discussion until after the 0.4
(which should come later this week) release, to make sure we have time to
find a nice design.


Cheers,
Spencer Janssen



More information about the Xmonad mailing list