diff --git a/XMonad/Doc/Extending.hs b/XMonad/Doc/Extending.hs index 825312f4..3a7250e5 100644 --- a/XMonad/Doc/Extending.hs +++ b/XMonad/Doc/Extending.hs @@ -21,25 +21,24 @@ -- Stability : unstable -- Portability : portable -- --- This module documents the xmonad-contrib library and --- how to use it to extend the capabilities of xmonad. +-- This module documents the xmonad-contrib library and guides you +-- through some more advanced parts of extending the capabilities of +-- xmonad. If you're new to xmonad, you should first check out the +-- and treat this document +-- as supplemental reading. -- --- Reading this document should not require a deep knowledge of --- Haskell; the examples are intended to be useful and understandable --- for those users who do not know Haskell and don't want to have to --- learn it just to configure xmonad. You should be able to get by --- just fine by ignoring anything you don't understand and using the --- provided examples as templates. However, relevant Haskell features --- are discussed when appropriate, so this document will hopefully be --- useful for more advanced Haskell users as well. +-- Knowing Haskell is by no means a prerequisite for configuring xmonad +-- and the tutorial emphasizes this. This document, however, does +-- assume a basic familiarity with the language. This is so that we can +-- dive a bit deeper into what the different hooks do, or how to write +-- our own little functions to configure xmonad. -- -- Those wishing to be totally hardcore and develop their own xmonad --- extensions (it's easier than it sounds, we promise!) should read --- the documentation in "XMonad.Doc.Developing". +-- extensions (it's easier than it sounds, we promise!) should read the +-- documentation in "XMonad.Doc.Developing". -- --- More configuration examples may be found on the Haskell wiki: --- --- +-- More configuration examples can be found +-- . -- ----------------------------------------------------------------------------- @@ -66,12 +65,9 @@ module XMonad.Doc.Extending -- * Extending xmonad -- $extending - -- ** Editing key bindings + -- ** Adding key bindings -- $keys - -- *** Adding key bindings - -- $keyAdding - -- *** Removing key bindings -- $keyDel @@ -99,10 +95,10 @@ module XMonad.Doc.Extending {- $library -The xmonad-contrib (xmc) library is a set of extension modules -contributed by xmonad hackers and users, which provide additional -xmonad features. Examples include various layout modes (tabbed, -spiral, three-column...), prompts, program launchers, the ability to +The xmonad-contrib library is a set of extension modules contributed +by xmonad hackers and users that provide additional features to +xmonad. Examples include various layout modes (tabbed, spiral, +three-column...), prompts, program launchers, the ability to manipulate windows and workspaces in various ways, alternate navigation modes, and much more. There are also \"meta-modules\" which make it easier to write new modules and extensions. @@ -143,9 +139,6 @@ various functions that are usually intended to be bound to key combinations or mouse actions, in order to provide functionality beyond the standard keybindings provided by xmonad. -See "XMonad.Doc.Extending#Editing_key_bindings" for instructions on how to -edit your key bindings. - -} {- $hooks @@ -154,23 +147,23 @@ In the @XMonad.Hooks@ namespace you can find modules exporting hooks. Hooks are actions that xmonad performs when certain events occur. The three most important hooks are: -* 'XMonad.Core.manageHook': this hook is called when a new window +* 'XMonad.Core.manageHook': this hook is called when a new window that xmonad must take care of is created. This is a very powerful hook, since it lets us examine the new window's properties and act accordingly. For instance, we can configure xmonad to put windows belonging to a given application in the float layer, not to manage dock applications, or open them in a given workspace. See - "XMonad.Doc.Extending#Editing_the_manage_hook" for more information on - customizing 'XMonad.Core.manageHook'. + "XMonad.Doc.Extending#Editing_the_manage_hook" for more information + on customizing 'XMonad.Core.manageHook'. * 'XMonad.Core.logHook': this hook is called when the stack of windows - managed by xmonad has been changed, by calling the - 'XMonad.Operations.windows' function. For instance + managed by xmonad has been changed; for example, this is invoked at + the end of the 'XMonad.Operations.windows' function. For instance "XMonad.Hooks.DynamicLog" will produce a string (whose format can be configured) to be printed to the standard output. This can be used to display some information about the xmonad state in a status bar. - See "XMonad.Doc.Extending#The_log_hook_and_external_status_bars" for more - information. + See "XMonad.Doc.Extending#The_log_hook_and_external_status_bars" for + more information. * 'XMonad.Core.handleEventHook': this hook is called on all events handled by xmonad, thus it is extremely powerful. See "Graphics.X11.Xlib.Extras" @@ -181,7 +174,7 @@ occur. The three most important hooks are: {- $layouts In the @XMonad.Layout@ namespace you can find modules exporting -contributed tiling algorithms, such as a tabbed layout, a circle, a spiral, +contributed layout algorithms, such as a tabbed layout, a circle, a spiral, three columns, and so on. You will also find modules which provide facilities for combining @@ -204,8 +197,8 @@ In the @XMonad.Prompt@ name space you can find modules providing graphical prompts for getting user input and using it to perform various actions. -The "XMonad.Prompt" provides a library for easily writing new prompt -modules. +The "XMonad.Prompt" module provides a library for easily writing new +prompts. -} @@ -237,124 +230,171 @@ yourself. -} {- $keys -#Editing_key_bindings# -Editing key bindings means changing the 'XMonad.Core.XConfig.keys' -field of the 'XMonad.Core.XConfig' record used by xmonad. For -example, you could write: +In the + +of the tutorial we have seen how to add new keys to xmonad with the help +of the 'XMonad.Util.EZConfig.additionalKeysP' function. But how does +that work? Assuming that library didn't exist yet, could we write it +ourselves? -> import XMonad +Let's concentrate on the easier case of trying to write our own +'XMonad.Util.EZConfig.additionalKeys'. This works exactly like its +almost-namesake, but requires you to specify the keys in the "default" +style—that is: + +> main :: IO () +> main = xmonad $ def +> `additionalKeys` +> [ ((mod1Mask, xK_m ), spawn "echo 'Hi, mom!' | dzen2 -p 4") +> , ((mod1Mask, xK_BackSpace), spawn "xterm") +> ] + +The extra work that 'XMonad.Util.EZConfig.additionalKeysP' does is only +in parsing the input string (turning @"M1-m"@ into @(mod1Mask, xK_m)@). +As we have seen in the tutorial, is also allows one to write @M@ and +have xmonad pick up on the correct modifier key to use—something which +'XMonad.Util.EZConfig.additionalKeys' can't do. + +Editing key bindings means changing the 'XMonad.Core.keys' field of the +'XMonad.Core.XConfig' record used by xmonad. For example, to override +/all/ of the default bindings with our own, we would write + +> import XMonad +> import Data.Map (Map) +> import qualified Data.Map as Map > -> main = xmonad $ def { keys = myKeys } +> main :: IO () +> main = xmonad $ def { keys = myKeys } +> where +> myKeys :: XConfig l -> Map (ButtonMask, KeySym) (X ()) +> myKeys conf = Map.fromList +> [ ((mod1Mask , xK_m ), spawn "echo 'Hi, mom!' | dzen2 -p 4") +> , ((modMask conf, xK_BackSpace), spawn "xterm") +> ] -and provide an appropriate definition of @myKeys@, such as: +Now, obviously we don't want to do that; we only want to add to existing +bindings (or, perhaps, override some of them with our own). Let's break +@myKeys@ down a little. You can think of the type signature of @myKeys@ +(and hence also of @keys@) like this: -> myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList -> [ ((modm, xK_F12), xmonadPrompt def) -> , ((modm, xK_F3 ), shellPrompt def) -> ] +> myKeys :: UserConfig -> Map KeyPress Action -This particular definition also requires importing "XMonad.Prompt", -"XMonad.Prompt.Shell", "XMonad.Prompt.XMonad", and "Data.Map": +It takes some user config and, from that, produces a map that associates +certain keypresses with actions to execute. The reason why it might +take the user config may seem a bit mysterious at first, but it is for +the simple reason that some keybindings (like the workspace switching +ones) need access to the user config. We have already seen this above +when we queried @modMask conf@. If it helps, think of this as a +@Reader@ monad with the config being the read-only state. -> import qualified Data.Map as M -> import XMonad.Prompt -> import XMonad.Prompt.Shell -> import XMonad.Prompt.XMonad +This means that, as a first guess, the type signature of our version of +'XMonad.Util.EZConfig.additionalKeys' might look like -For a list of the names of particular keys (such as xK_F12, and so -on), see - +> myAdditionalKeys :: XConfig l +> -- ^ Base config with xmonad's default keybindings +> -> (XConfig l -> Map (ButtonMask, KeySym) (X ())) +> -- ^ User supplied keybindings +> -> XConfig l +> -- ^ Resulting config with everything merged together -Usually, rather than completely redefining the key bindings, as we did -above, we want to simply add some new bindings and\/or remove existing -ones. +However, even assuming a correct implementation, using this is not very +ergonomic: --} +> main = xmonad $ def +> `myAdditionalKeys` +> (\conf -> Map.fromList +> [ ((mod1Mask , xK_m ), spawn "echo 'Hi, mom!' | dzen2 -p 4") +> , ((modMask conf, xK_BackSpace), spawn "xterm") +> ]) -{- $keyAdding -#Adding_key_bindings# +Having to specify a lambda with parentheses and call +'Data.Map.Strict.fromList' does not make for a good user experience. +Since one /always/ has to call that function anyways, we may well just +accept a list from the user and transform it to a map ourselves. As an +additional simplification, how about we don't care about the config +argument at all and simply ask the user for a list? The resulting +signature -Adding key bindings can be done in different ways. See the end of this -section for the easiest ways. The type signature of -'XMonad.Core.XConfig.keys' is: +> myAdditionalKeys :: XConfig l +> -> [(ButtonMask, KeySym), (X ())] +> -> XConfig l -> keys :: XConfig Layout -> M.Map (ButtonMask,KeySym) (X ()) +looks exactly like what we want! Note that this is also the time we +lose the ability to automagically fill in the correct modifier key, +since the input to @myAdditionalKeys@ is already structured data (as +opposed to just some strings that need to be parsed). -In order to add new key bindings, you need to first create an -appropriate 'Data.Map.Map' from a list of key bindings using -'Data.Map.fromList'. This 'Data.Map.Map' of new key bindings then -needs to be joined to a 'Data.Map.Map' of existing bindings using -'Data.Map.union'. +Now that we know what kind of data structure—that is, maps—we are +dealing with, the implementation of this function simply merges the two +together, preferring the user config to xmonad's defaults in case of any +conflicts. Thankfully, someone else has already done the hard work and +written the merging function for us; it's called +'Data.Map.Strict.union'. -Since we are going to need some of the functions of the "Data.Map" -module, before starting we must first import this modules: +What's left is essentially playing "type tetris": -> import qualified Data.Map as M +> myAdditionalKeys baseConf keyList = +> let mergeKeylist conf = Map.fromList keyList `Map.union` (keys baseConf) conf +> in baseConf { keys = mergeKeylist } +The function @mergeKeyList@ take some user config, transforms the custom +keybindings into a map (@Map.fromList keyList@), gets the keys from the +base config (remember @keys baseConf@ is again a function, morally of +type @UserConfig -> Map KeyPress Action@, and so we have to apply @conf@ +to it in order to get a map!), and then merges these two maps together. +Since @mergeKeylist@ now has exactly the right type signature, we can +just set that as the keys. -For instance, if you have defined some additional key bindings like -these: +If you like operators, 'Data.Monoid.<>' (or xmonad's alias for it, +'XMonad.ManageHook.<+>') does exactly the same as the explicit usage of +'Data.Map.Strict.union' because that's the specified binary operation in +the 'Data.Monoid.Monoid' instance for 'Data.Map.Strict.Map'. Note that +the function works as expected (preferring user defined keys) because +'Data.Map.union' is /left biased/, which means that if the same key is +present in both maps it will prefer the associated value of the left +map. -> myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList -> [ ((modm, xK_F12), xmonadPrompt def) -> , ((modm, xK_F3 ), shellPrompt def) -> ] +Our function now works as expected: -then you can create a new key bindings map by joining the default one -with yours: +> main :: IO () +> main = xmonad $ def +> `myAdditionalKeys` +> [ ((mod1Mask, xK_m ), spawn "echo 'Hi, mom!' | dzen2 -p 4") +> , ((mod1Mask, xK_BackSpace), spawn "xterm") +> ] -> newKeys x = myKeys x `M.union` keys def x +Lastly, if you want you can also emulate the automatic modifier +detection by 'XMonad.Util.EZConfig.additionalKeysP' by defining the bulk +of your config as a separate function -Finally, you can use @newKeys@ in the 'XMonad.Core.XConfig.keys' field -of the configuration: +> myConfig = def { modMask = mod4Mask } -> main = xmonad $ def { keys = newKeys } +and then using that information -Alternatively, the '<+>' operator can be used which in this usage does exactly -the same as the explicit usage of 'M.union' and propagation of the config -argument, thanks to appropriate instances in "Data.Monoid". +> main :: IO () +> main = xmonad $ myConfig +> `myAdditionalKeys` +> [ ((mod, xK_m ), spawn "echo 'Hi, mom!' | dzen2 -p 4") +> , ((mod, xK_BackSpace), spawn "xterm") +> ] +> where mod = modMask myConfig -> main = xmonad $ def { keys = myKeys <+> keys def } - -All together, your @~\/.xmonad\/xmonad.hs@ would now look like this: - - -> module Main (main) where -> -> import XMonad -> -> import qualified Data.Map as M -> import Graphics.X11.Xlib -> import XMonad.Prompt -> import XMonad.Prompt.Shell -> import XMonad.Prompt.XMonad -> -> main :: IO () -> main = xmonad $ def { keys = myKeys <+> keys def } -> -> myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList -> [ ((modm, xK_F12), xmonadPrompt def) -> , ((modm, xK_F3 ), shellPrompt def) -> ] - -There are much simpler ways to accomplish this, however, if you are -willing to use an extension module to help you configure your keys. -For instance, "XMonad.Util.EZConfig" and "XMonad.Util.CustomKeys" both -provide useful functions for editing your key bindings; "XMonad.Util.EZConfig" even lets you use emacs-style keybinding descriptions like \"M-C-\". +Hopefully you now feel well equipped to write some small functions that +extend xmonad an scratch a particular itch! -} {- $keyDel #Removing_key_bindings# -Removing key bindings requires modifying the 'Data.Map.Map' which -stores the key bindings. This can be done with 'Data.Map.difference' -or with 'Data.Map.delete'. +Removing key bindings requires modifying the 'Data.Map.Strict.Map' which +stores the key bindings. This can be done with 'Data.Map.difference' or +with 'Data.Map.Strict.delete'. For example, suppose you want to get rid of @mod-q@ and @mod-shift-q@ -(you just want to leave xmonad running forever). To do this you need -to define @newKeys@ as a 'Data.Map.difference' between the default +(you just want to leave xmonad running forever). To do this you need to +define @newKeys@ as a 'Data.Map.Strict.difference' between the default map and the map of the key bindings you want to remove. Like so: > newKeys x = keys def x `M.difference` keysToRemove x @@ -370,8 +410,8 @@ keys listed in @keysToRemove@, so we just use @return ()@ (the \"null\" action). It is also possible to simply define a list of keys we want to unbind -and then use 'Data.Map.delete' to remove them. In that case we would -write something like: +and then use 'Data.Map.Strict.delete' to remove them. In that case we +would write something like: > newKeys x = foldr M.delete (keys def x) (keysToRemove x) > @@ -468,9 +508,8 @@ advanced feature of the Haskell programming language: type classes. This allows us to very easily write new layouts, combine or modify existing layouts, create layouts with internal state, etc. See "XMonad.Doc.Extending#The_LayoutClass" for more information. This -means that we cannot simply have a list of layouts as we used to have -before the 0.5 release: a list requires every member to belong to the -same type! +means that we cannot simply have a list of layouts: a list requires +every member to belong to the same type! Instead the combination of layouts to be used by xmonad is created with a specific layout combinator: 'XMonad.Layout.|||'. @@ -665,11 +704,11 @@ And then we can add @myManageHook@ to the default one to create One more thing to note about this system is that if a window matches multiple rules in a 'XMonad.Config.manageHook', /all/ of the corresponding actions will be run (in the order in which they -are defined). This is a change from versions before 0.5, when only -the first rule that matched was run. +are defined). An alternative version where only the first rule that +matches is run is available as 'XMonad.Hooks.ManageHelpers.composeOne'. -Finally, for additional rules and actions you can use in your -manageHook, check out the contrib module "XMonad.Hooks.ManageHelpers". +For additional rules and actions you can use in your manageHook, check +out the contrib module "XMonad.Hooks.ManageHelpers". -}