More user-friendly hook system

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@joachim-breitner.de Homepage: http://www.joachim-breitner.de ICQ#: 74513189

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

On Wed, Oct 10, 2007 at 10:48:52AM -0500, Spencer Janssen wrote:
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.
Also, note that these proposals could all be implemented in a contrib module as a function such as: combineHooks :: [... -> ... -> ...] -> ... -> ... so it's hard to see why we'd want to put this in the core (unless the core wants it). -- David Roundy Department of Physics Oregon State University

Hi, Am Mittwoch, den 10.10.2007, 11:56 -0400 schrieb David Roundy:
On Wed, Oct 10, 2007 at 10:48:52AM -0500, Spencer Janssen wrote:
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.
Also, note that these proposals could all be implemented in a contrib module as a function such as:
combineHooks :: [... -> ... -> ...] -> ... -> ...
so it's hard to see why we'd want to put this in the core (unless the core wants it).
We still would need the record definition in the core, as that is where hooks are called and where new hooks are added. And I think requiring the user to write
import XMonadContrib.CombineHooks
xmonadHook = combineHooks [ .. ]
instead of just
xmonadHooks = [ .. ]
whenever they want to use more than one extension, just to safe half a line of code in all the places where hooks are called does not seem to be worth it. If we’d add a
instance Monoid XMonadHook where ..
then combining the hooks is just a matter of using
mconcat Config.xmonadHooks instead of mconcat Config.xmonadHook so really no hassle, I’d say.
I agree with Spencer that this should not go in before the next release, though. Greetings, Joachim -- Joachim "nomeata" Breitner mail: mail@joachim-breitner.de | ICQ# 74513189 | GPG-Key: 4743206C JID: joachimbreitner@amessage.de | http://www.joachim-breitner.de/ Debian Developer: nomeata@debian.org

On Wed, Oct 10, 2007 at 04:04:36PM +0000, Joachim Breitner wrote:
Hi,
Am Mittwoch, den 10.10.2007, 11:56 -0400 schrieb David Roundy:
On Wed, Oct 10, 2007 at 10:48:52AM -0500, Spencer Janssen wrote:
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.
Also, note that these proposals could all be implemented in a contrib module as a function such as:
combineHooks :: [... -> ... -> ...] -> ... -> ...
so it's hard to see why we'd want to put this in the core (unless the core wants it).
We still would need the record definition in the core, as that is where hooks are called and where new hooks are added. And I think requiring the user to write
No, the core would remain unchanged, since the core API is unchanged, and the record would be defined in Contrib. Why would you need to define a record for your hook type in the core?
import XMonadContrib.CombineHooks
xmonadHook = combineHooks [ .. ]
instead of just
xmonadHooks = [ .. ]
whenever they want to use more than one extension, just to safe half a line of code in all the places where hooks are called does not seem to be worth it.
I'm not convinced that there will be such a proliferation of hooks that many users will ever do this. I suppose that's largely because I don't yet use any myself, and haven't seen any reason to define any. I'd rather see the core API only complicated when there's a feature that can't be added without the complication (as happened with layouts) or when it makes things much simpler (or is needed for a reasonable core functionality, as with LayoutSelection). I'd say that one line and a dozen characters added to the Config file is well worth the ability to experiment freely, and allowing us to see and try out code. -- David Roundy Department of Physics Oregon State University

Hi, Am Mittwoch, den 10.10.2007, 14:04 -0400 schrieb David Roundy:
We still would need the record definition in the core, as that is where hooks are called and where new hooks are added. And I think requiring the user to write
No, the core would remain unchanged, since the core API is unchanged, and the record would be defined in Contrib. Why would you need to define a record for your hook type in the core?
Because that’s where hooks are defined, after all. If we’d leave this to the Config file writer, then he’d still have to adjust the configuration file for each new hook that the core defines, which is what I want to avoid.
import XMonadContrib.CombineHooks
xmonadHook = combineHooks [ .. ]
instead of just
xmonadHooks = [ .. ]
whenever they want to use more than one extension, just to safe half a line of code in all the places where hooks are called does not seem to be worth it.
I'm not convinced that there will be such a proliferation of hooks that many users will ever do this. I suppose that's largely because I don't yet use any myself, and haven't seen any reason to define any.
I'd rather see the core API only complicated when there's a feature that can't be added without the complication (as happened with layouts) or when it makes things much simpler (or is needed for a reasonable core functionality, as with LayoutSelection).
I'd say that one line and a dozen characters added to the Config file is well worth the ability to experiment freely, and allowing us to see and try out code.
I think introducing a clean, simple interface for hooks for the user, and hiding the details in the type system means that just that: you can freely experiment (adding new hooks, changing the way hooks are implemented) without having the user adjust his configuration file. I don’t see how that complicates the core. Also note that there is a proliferation of hooks. At first, it was only the definition of key commands. Then came manageHooks. Later logHooks appeared. I’d almost bet that someone will want a initHook that runs once at start up. For propoer gap-STRUT-handling, an unmanageHook would be needed as well. I’d like to be able to add these hooks without breaking existing hooks and extensions, but also without having the user to figure out manually what extensions expose functions for what hooks and how to combine hooks from different extensions. I’d also like to stress that I’m proposing an interface for the Configuration and a possible implementation − if someone comes up with a more suitable implementation, even better! Greetings, Joachim -- Joachim "nomeata" Breitner mail: mail@joachim-breitner.de | ICQ# 74513189 | GPG-Key: 4743206C JID: joachimbreitner@amessage.de | http://www.joachim-breitner.de/ Debian Developer: nomeata@debian.org

On Wed, Oct 10, 2007 at 06:51:07PM +0000, Joachim Breitner wrote:
Hi,
Am Mittwoch, den 10.10.2007, 14:04 -0400 schrieb David Roundy:
We still would need the record definition in the core, as that is where hooks are called and where new hooks are added. And I think requiring the user to write
No, the core would remain unchanged, since the core API is unchanged, and the record would be defined in Contrib. Why would you need to define a record for your hook type in the core?
Because that's where hooks are defined, after all. If we'd leave this to the Config file writer, then he'd still have to adjust the configuration file for each new hook that the core defines, which is what I want to avoid.
Where are the hooks defined? You mean the default hooks that are in Config.hs? That's easily handled by renaming that function or duplicating it in Contrib for those who want extra-easy ultimate power.
import XMonadContrib.CombineHooks
xmonadHook = combineHooks [ .. ]
instead of just
xmonadHooks = [ .. ]
whenever they want to use more than one extension, just to safe half a line of code in all the places where hooks are called does not seem to be worth it.
I'm not convinced that there will be such a proliferation of hooks that many users will ever do this. I suppose that's largely because I don't yet use any myself, and haven't seen any reason to define any.
I'd rather see the core API only complicated when there's a feature that can't be added without the complication (as happened with layouts) or when it makes things much simpler (or is needed for a reasonable core functionality, as with LayoutSelection).
I'd say that one line and a dozen characters added to the Config file is well worth the ability to experiment freely, and allowing us to see and try out code.
I think introducing a clean, simple interface for hooks for the user, and hiding the details in the type system means that just that: you can freely experiment (adding new hooks, changing the way hooks are implemented) without having the user adjust his configuration file. I don't see how that complicates the core.
If you're right, then people will use your system, when you introduce it to contrib. If one line of code added to their Config.hs is too much to make it worthwhile, I doubt it's going to proliferate anyhow.
Also note that there is a proliferation of hooks. At first, it was only the definition of key commands. Then came manageHooks. Later logHooks appeared. I'd almost bet that someone will want a initHook that runs once at start up. For propoer gap-STRUT-handling, an unmanageHook would be needed as well. I'd like to be able to add these hooks without breaking existing hooks and extensions, but also without having the user to figure out manually what extensions expose functions for what hooks and how to combine hooks from different extensions.
Yes, there are a number of different hook functions defined, but they don't require combinators to be useful. You're proposing an infrastructure for which it's not clear there's a need. You're proposing an API without a use (yet).
I'd also like to stress that I'm proposing an interface for the Configuration and a possible implementation -- if someone comes up with a more suitable implementation, even better!
And what I'm saying is that the best way to propose an interface is with actual code for said interface, and the best way to do this is in contrib. If it's worthwhile, it'll stick around, and if it's small and elegant, it'll be moved to main. -- David Roundy Department of Physics Oregon State University

I think introducing a clean, simple interface for hooks for the user, and hiding the details in the type system means that just that: you can freely experiment (adding new hooks, changing the way hooks are implemented) without having the user adjust his configuration file. I don't see how that complicates the core.
If you're right, then people will use your system, when you introduce it to contrib. If one line of code added to their Config.hs is too much to make it worthwhile, I doubt it's going to proliferate anyhow.
Also note that there is a proliferation of hooks. At first, it was only the definition of key commands. Then came manageHooks. Later logHooks appeared. I'd almost bet that someone will want a initHook that runs once at start up. For propoer gap-STRUT-handling, an unmanageHook would be needed as well. I'd like to be able to add these hooks without breaking existing hooks and extensions, but also without having the user to figure out manually what extensions expose functions for what hooks and how to combine hooks from different extensions.
Yes, there are a number of different hook functions defined, but they don't require combinators to be useful. You're proposing an infrastructure for which it's not clear there's a need. You're proposing an API without a use (yet).
I think there is quite some use for it: Currently eight modules in Contrib seem to use hooks. And I know at least three possible hooks that should probably be added to allow more features to implemented in contrib modules (initHook, unmanageHook, clientMessageHook[1]). It does not feel right to have not a clear interface for adding new hooks, which is what stopped me from implementing features needing these hooks. So I think there is a need for an API.
I'd also like to stress that I'm proposing an interface for the Configuration and a possible implementation -- if someone comes up with a more suitable implementation, even better!
And what I'm saying is that the best way to propose an interface is with actual code for said interface, and the best way to do this is in contrib. If it's worthwhile, it'll stick around, and if it's small and elegant, it'll be moved to main.
I can understand that you want these things tested in contrib first, and I think I’ll create something for conrib once 0.4 is out. But then the users have to manually plug each existing hook (currently manageHook and logHook) into the new system manually, and the hook internals would not be hidden from the user, so two goals of my proposal can’t be really fulfilled without any changes to the core. But maybe this discussion should be postboned till after 0.4, when we will have code to talk about. Thanks, Joachim [1] Refering to the XEvent ClientMessage, needed for Ewmh compatibility. -- Joachim "nomeata" Breitner mail: mail@joachim-breitner.de | ICQ# 74513189 | GPG-Key: 4743206C JID: joachimbreitner@amessage.de | http://www.joachim-breitner.de/ Debian Developer: nomeata@debian.org

On Wed, Oct 10, 2007 at 10:22:45PM +0200, Joachim Breitner wrote:
Yes, there are a number of different hook functions defined, but they don't require combinators to be useful. You're proposing an infrastructure for which it's not clear there's a need. You're proposing an API without a use (yet).
I think there is quite some use for it: Currently eight modules in Contrib seem to use hooks. And I know at least three possible hooks that should probably be added to allow more features to implemented in contrib modules (initHook, unmanageHook, clientMessageHook[1]). It does not feel right to have not a clear interface for adding new hooks, which is what stopped me from implementing features needing these hooks. So I think there is a need for an API.
Your propose API is only needed if there's a need for users to easily compose hooks.
I'd also like to stress that I'm proposing an interface for the Configuration and a possible implementation -- if someone comes up with a more suitable implementation, even better!
And what I'm saying is that the best way to propose an interface is with actual code for said interface, and the best way to do this is in contrib. If it's worthwhile, it'll stick around, and if it's small and elegant, it'll be moved to main.
I can understand that you want these things tested in contrib first, and I think I'll create something for conrib once 0.4 is out. But then the users have to manually plug each existing hook (currently manageHook and logHook) into the new system manually, and the hook internals would not be hidden from the user, so two goals of my proposal can't be really fulfilled without any changes to the core.
So your two goals are: 1) Make it easier to add lots of new hooks into the core. 2) Make it easier to compose contrib functions that define new functionality using hooks. Adding new hooks to the core certainly requires changes to core, but is already very easy. The hard part is convincing people that it's a good idea. Of course, new hooks for responding to X events that aren't handled by core could also be added through contrib, via layout modifiers. If the new hooks seem useful, they could be added to the core. The second goal requires no changes to core.
But maybe this discussion should be postboned till after 0.4, when we will have code to talk about.
Yes, waiting for code does make sense.
[1] Refering to the XEvent ClientMessage, needed for Ewmh compatibility.
What does this do/need to do? -- David Roundy Department of Physics Oregon State University
participants (3)
-
David Roundy
-
Joachim Breitner
-
Spencer Janssen