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
-- 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
-- <https://xmonad.org/TUTORIAL.html tutorial> 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:
--
-- <http://haskell.org/haskellwiki/Xmonad/Config_archive>
-- More configuration examples can be found
-- <https://xmonad.org/TUTORIAL.html#closing-thoughts here>.
--
-----------------------------------------------------------------------------
@ -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
<https://github.com/xmonad/xmonad/blob/master/TUTORIAL.md#customizing-xmonad customization section>
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
>
> main = xmonad $ def { keys = myKeys }
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"
stylethat is:
and provide an appropriate definition of @myKeys@, such as:
> myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList
> [ ((modm, xK_F12), xmonadPrompt def)
> , ((modm, xK_F3 ), shellPrompt def)
> main :: IO ()
> main = xmonad $ def
> `additionalKeys`
> [ ((mod1Mask, xK_m ), spawn "echo 'Hi, mom!' | dzen2 -p 4")
> , ((mod1Mask, xK_BackSpace), spawn "xterm")
> ]
This particular definition also requires importing "XMonad.Prompt",
"XMonad.Prompt.Shell", "XMonad.Prompt.XMonad", and "Data.Map":
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 usesomething which
'XMonad.Util.EZConfig.additionalKeys' can't do.
> import qualified Data.Map as M
> import XMonad.Prompt
> import XMonad.Prompt.Shell
> import XMonad.Prompt.XMonad
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
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 qualified Data.Map as M
> import Graphics.X11.Xlib
> import XMonad.Prompt
> import XMonad.Prompt.Shell
> import XMonad.Prompt.XMonad
> import Data.Map (Map)
> import qualified Data.Map as Map
>
> 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)
> 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")
> ]
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-<F12>\".
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 :: 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
#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".
-}