Merge pull request #637 from slotThe/modernise-extending

Start modernising X.D.Extending
This commit is contained in:
slotThe 2021-11-10 11:47:31 +01:00 committed by GitHub
commit 67a92edd2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -21,25 +21,24 @@
-- Stability : unstable -- Stability : unstable
-- Portability : portable -- Portability : portable
-- --
-- This module documents the xmonad-contrib library and -- This module documents the xmonad-contrib library and guides you
-- how to use it to extend the capabilities of xmonad. -- through some more advanced parts of extending the capabilities of
-- xmonad. If you're new to xmonad, you should first check out the
-- <https://xmonad.org/TUTORIAL.html tutorial> and treat this document
-- as supplemental reading.
-- --
-- Reading this document should not require a deep knowledge of -- Knowing Haskell is by no means a prerequisite for configuring xmonad
-- Haskell; the examples are intended to be useful and understandable -- and the tutorial emphasizes this. This document, however, does
-- for those users who do not know Haskell and don't want to have to -- assume a basic familiarity with the language. This is so that we can
-- learn it just to configure xmonad. You should be able to get by -- dive a bit deeper into what the different hooks do, or how to write
-- just fine by ignoring anything you don't understand and using the -- our own little functions to configure xmonad.
-- 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.
-- --
-- Those wishing to be totally hardcore and develop their own xmonad -- Those wishing to be totally hardcore and develop their own xmonad
-- extensions (it's easier than it sounds, we promise!) should read -- extensions (it's easier than it sounds, we promise!) should read the
-- the documentation in "XMonad.Doc.Developing". -- documentation in "XMonad.Doc.Developing".
-- --
-- More configuration examples may be found on the Haskell wiki: -- More configuration examples can be found
-- -- <https://xmonad.org/TUTORIAL.html#closing-thoughts here>.
-- <http://haskell.org/haskellwiki/Xmonad/Config_archive>
-- --
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@ -66,12 +65,9 @@ module XMonad.Doc.Extending
-- * Extending xmonad -- * Extending xmonad
-- $extending -- $extending
-- ** Editing key bindings -- ** Adding key bindings
-- $keys -- $keys
-- *** Adding key bindings
-- $keyAdding
-- *** Removing key bindings -- *** Removing key bindings
-- $keyDel -- $keyDel
@ -99,10 +95,10 @@ module XMonad.Doc.Extending
{- $library {- $library
The xmonad-contrib (xmc) library is a set of extension modules The xmonad-contrib library is a set of extension modules contributed
contributed by xmonad hackers and users, which provide additional by xmonad hackers and users that provide additional features to
xmonad features. Examples include various layout modes (tabbed, xmonad. Examples include various layout modes (tabbed, spiral,
spiral, three-column...), prompts, program launchers, the ability to three-column...), prompts, program launchers, the ability to
manipulate windows and workspaces in various ways, alternate manipulate windows and workspaces in various ways, alternate
navigation modes, and much more. There are also \"meta-modules\" navigation modes, and much more. There are also \"meta-modules\"
which make it easier to write new modules and extensions. 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 combinations or mouse actions, in order to provide functionality
beyond the standard keybindings provided by xmonad. beyond the standard keybindings provided by xmonad.
See "XMonad.Doc.Extending#Editing_key_bindings" for instructions on how to
edit your key bindings.
-} -}
{- $hooks {- $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 hooks. Hooks are actions that xmonad performs when certain events
occur. The three most important hooks are: 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, 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 since it lets us examine the new window's properties and act
accordingly. For instance, we can configure xmonad to put windows accordingly. For instance, we can configure xmonad to put windows
belonging to a given application in the float layer, not to manage belonging to a given application in the float layer, not to manage
dock applications, or open them in a given workspace. See dock applications, or open them in a given workspace. See
"XMonad.Doc.Extending#Editing_the_manage_hook" for more information on "XMonad.Doc.Extending#Editing_the_manage_hook" for more information
customizing 'XMonad.Core.manageHook'. on customizing 'XMonad.Core.manageHook'.
* 'XMonad.Core.logHook': this hook is called when the stack of windows * 'XMonad.Core.logHook': this hook is called when the stack of windows
managed by xmonad has been changed, by calling the managed by xmonad has been changed; for example, this is invoked at
'XMonad.Operations.windows' function. For instance the end of the 'XMonad.Operations.windows' function. For instance
"XMonad.Hooks.DynamicLog" will produce a string (whose format can be "XMonad.Hooks.DynamicLog" will produce a string (whose format can be
configured) to be printed to the standard output. This can be used configured) to be printed to the standard output. This can be used
to display some information about the xmonad state in a status bar. 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 See "XMonad.Doc.Extending#The_log_hook_and_external_status_bars" for
information. more information.
* 'XMonad.Core.handleEventHook': this hook is called on all events handled * 'XMonad.Core.handleEventHook': this hook is called on all events handled
by xmonad, thus it is extremely powerful. See "Graphics.X11.Xlib.Extras" by xmonad, thus it is extremely powerful. See "Graphics.X11.Xlib.Extras"
@ -181,7 +174,7 @@ occur. The three most important hooks are:
{- $layouts {- $layouts
In the @XMonad.Layout@ namespace you can find modules exporting 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. three columns, and so on.
You will also find modules which provide facilities for combining 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 graphical prompts for getting user input and using it to perform
various actions. various actions.
The "XMonad.Prompt" provides a library for easily writing new prompt The "XMonad.Prompt" module provides a library for easily writing new
modules. prompts.
-} -}
@ -237,124 +230,171 @@ yourself.
-} -}
{- $keys {- $keys
#Editing_key_bindings#
Editing key bindings means changing the 'XMonad.Core.XConfig.keys' In the
field of the 'XMonad.Core.XConfig' record used by xmonad. For <https://github.com/xmonad/xmonad/blob/master/TUTORIAL.md#customizing-xmonad customization section>
example, you could write: 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
> main = xmonad $ def { keys = myKeys } almost-namesake, but requires you to specify the keys in the "default"
stylethat is:
and provide an appropriate definition of @myKeys@, such as: > main :: IO ()
> main = xmonad $ def
> myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList > `additionalKeys`
> [ ((modm, xK_F12), xmonadPrompt def) > [ ((mod1Mask, xK_m ), spawn "echo 'Hi, mom!' | dzen2 -p 4")
> , ((modm, xK_F3 ), shellPrompt def) > , ((mod1Mask, xK_BackSpace), spawn "xterm")
> ] > ]
This particular definition also requires importing "XMonad.Prompt", The extra work that 'XMonad.Util.EZConfig.additionalKeysP' does is only
"XMonad.Prompt.Shell", "XMonad.Prompt.XMonad", and "Data.Map": 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 usesomething which
'XMonad.Util.EZConfig.additionalKeys' can't do.
> import qualified Data.Map as M Editing key bindings means changing the 'XMonad.Core.keys' field of the
> import XMonad.Prompt 'XMonad.Core.XConfig' record used by xmonad. For example, to override
> import XMonad.Prompt.Shell /all/ of the default bindings with our own, we would write
> import XMonad.Prompt.XMonad
For a list of the names of particular keys (such as xK_F12, and so
on), see
<http://hackage.haskell.org/packages/archive/X11/latest/doc/html/Graphics-X11-Types.html>
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.
-}
{- $keyAdding
#Adding_key_bindings#
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:
> keys :: XConfig Layout -> M.Map (ButtonMask,KeySym) (X ())
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'.
Since we are going to need some of the functions of the "Data.Map"
module, before starting we must first import this modules:
> import qualified Data.Map as M
For instance, if you have defined some additional key bindings like
these:
> myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList
> [ ((modm, xK_F12), xmonadPrompt def)
> , ((modm, xK_F3 ), shellPrompt def)
> ]
then you can create a new key bindings map by joining the default one
with yours:
> newKeys x = myKeys x `M.union` keys def x
Finally, you can use @newKeys@ in the 'XMonad.Core.XConfig.keys' field
of the configuration:
> main = xmonad $ def { keys = newKeys }
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 = xmonad $ def { keys = myKeys <+> keys def }
All together, your @~\/.xmonad\/xmonad.hs@ would now look like this:
> module Main (main) where
>
> import XMonad > import XMonad
> > import Data.Map (Map)
> import qualified Data.Map as M > import qualified Data.Map as Map
> import Graphics.X11.Xlib
> import XMonad.Prompt
> import XMonad.Prompt.Shell
> import XMonad.Prompt.XMonad
> >
> main :: IO () > main :: IO ()
> main = xmonad $ def { keys = myKeys <+> keys def } > main = xmonad $ def { keys = myKeys }
> > where
> myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList > myKeys :: XConfig l -> Map (ButtonMask, KeySym) (X ())
> [ ((modm, xK_F12), xmonadPrompt def) > myKeys conf = Map.fromList
> , ((modm, xK_F3 ), shellPrompt def) > [ ((mod1Mask , xK_m ), spawn "echo 'Hi, mom!' | dzen2 -p 4")
> , ((modMask conf, xK_BackSpace), spawn "xterm")
> ] > ]
There are much simpler ways to accomplish this, however, if you are Now, obviously we don't want to do that; we only want to add to existing
willing to use an extension module to help you configure your keys. bindings (or, perhaps, override some of them with our own). Let's break
For instance, "XMonad.Util.EZConfig" and "XMonad.Util.CustomKeys" both @myKeys@ down a little. You can think of the type signature of @myKeys@
provide useful functions for editing your key bindings; "XMonad.Util.EZConfig" even lets you use emacs-style keybinding descriptions like \"M-C-<F12>\". (and hence also of @keys@) like this:
> myKeys :: UserConfig -> Map KeyPress Action
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.
This means that, as a first guess, the type signature of our version of
'XMonad.Util.EZConfig.additionalKeys' might look like
> 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
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")
> ])
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
> myAdditionalKeys :: XConfig l
> -> [(ButtonMask, KeySym), (X ())]
> -> XConfig l
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).
Now that we know what kind of data structurethat is, mapswe 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'.
What's left is essentially playing "type tetris":
> 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.
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.
Our function now works as expected:
> main :: IO ()
> main = xmonad $ def
> `myAdditionalKeys`
> [ ((mod1Mask, xK_m ), spawn "echo 'Hi, mom!' | dzen2 -p 4")
> , ((mod1Mask, xK_BackSpace), spawn "xterm")
> ]
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
> myConfig = def { modMask = mod4Mask }
and then using that information
> 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
Hopefully you now feel well equipped to write some small functions that
extend xmonad an scratch a particular itch!
-} -}
{- $keyDel {- $keyDel
#Removing_key_bindings# #Removing_key_bindings#
Removing key bindings requires modifying the 'Data.Map.Map' which Removing key bindings requires modifying the 'Data.Map.Strict.Map' which
stores the key bindings. This can be done with 'Data.Map.difference' stores the key bindings. This can be done with 'Data.Map.difference' or
or with 'Data.Map.delete'. with 'Data.Map.Strict.delete'.
For example, suppose you want to get rid of @mod-q@ and @mod-shift-q@ 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 (you just want to leave xmonad running forever). To do this you need to
to define @newKeys@ as a 'Data.Map.difference' between the default 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: map and the map of the key bindings you want to remove. Like so:
> newKeys x = keys def x `M.difference` keysToRemove x > 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). \"null\" action).
It is also possible to simply define a list of keys we want to unbind 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 and then use 'Data.Map.Strict.delete' to remove them. In that case we
write something like: would write something like:
> newKeys x = foldr M.delete (keys def x) (keysToRemove x) > 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 This allows us to very easily write new layouts, combine or modify
existing layouts, create layouts with internal state, etc. See existing layouts, create layouts with internal state, etc. See
"XMonad.Doc.Extending#The_LayoutClass" for more information. This "XMonad.Doc.Extending#The_LayoutClass" for more information. This
means that we cannot simply have a list of layouts as we used to have means that we cannot simply have a list of layouts: a list requires
before the 0.5 release: a list requires every member to belong to the every member to belong to the same type!
same type!
Instead the combination of layouts to be used by xmonad is created Instead the combination of layouts to be used by xmonad is created
with a specific layout combinator: 'XMonad.Layout.|||'. 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 One more thing to note about this system is that if
a window matches multiple rules in a 'XMonad.Config.manageHook', /all/ a window matches multiple rules in a 'XMonad.Config.manageHook', /all/
of the corresponding actions will be run (in the order in which they 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 are defined). An alternative version where only the first rule that
the first rule that matched was run. matches is run is available as 'XMonad.Hooks.ManageHelpers.composeOne'.
Finally, for additional rules and actions you can use in your For additional rules and actions you can use in your manageHook, check
manageHook, check out the contrib module "XMonad.Hooks.ManageHelpers". out the contrib module "XMonad.Hooks.ManageHelpers".
-} -}