mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-05-19 11:30:22 -07:00
Merge pull request #637 from slotThe/modernise-extending
Start modernising X.D.Extending
This commit is contained in:
commit
67a92edd2a
@ -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
|
||||
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
|
||||
<http://hackage.haskell.org/packages/archive/X11/latest/doc/html/Graphics-X11-Types.html>
|
||||
> 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-<F12>\".
|
||||
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".
|
||||
|
||||
-}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user