Xmonad: Catch all keyboard shortcut

keyboard shortcutsxmonad

I'm using Xmonad and I have a quite long list of custom keyboard short cuts defined in my config.

I would like to add something like a catch all at the bottom of the config which locks my screen if anybody presses a key combination that isn't defined, so any Ctrl+* or Alt+* which isn't defined to do something else should just lock the screen.

Is there any good way to do that without having to list all of the currently undefined combos?

Best Answer

Is there any good way to do that without having to list all of the currently undefined combos?

You should understand that it's not simply "lock on any unknown combo". You probably don't want to block things like Control-R that lets you reverse-search your shell's history, or Control-P that opens printing dialogue in your favourite office suite.

With that said, the cleanest way to achieve your goal is to redefine how keybindings are looked up in XMonad so that any "not founds" turn into "lock the screen". Unfortunatelly, XMonad doesn't provide such facilities to the user; you'll have to hack on the XMonad itself.

The next best thing you can do is generate a map of all valid key combos, strip it of ones you already have defined, and then bind all the remaining ones to "lock screen" command. Fortunatelly, this can all be automated.

I'll assume that you have your screen-locking command defined like that:

lockScreen = spawn "i3lock"

Key combinations consist of a mask and a key, which are all listed in Graphics.X11.Types.

First, let's define a list of possible masks:

masks = [controlMask, mod1Mask]

Here I assumed that you only want to block combos starting with Control and Alt. There are 8 masks, and they can be combined, so this line could be much more complicated. However, the more masks we define here, the more careful we should be when generating bindings—we don't want to accidentially block something important!

Okay, next stop is list of keys. These are listed in Graphics.X11.Types, too, and start with xK_. They have type KeySym, but what they really are is Word64. However, I advise not to go crazy and avoid writing something like [0 .. maxBound :: KeySym]—this will result in a huge map that will consume a lot of memory.

So instead, just look up the numbers of keys you want to cover and group those into small definitions. Here, for example, I cover the most common keys:

keys :: [KeySym]
keys = [xK_Home     .. xK_Num_Lock] ++  -- Cursor control & motion
       [xK_KP_Space .. xK_R15] ++       -- Keypad and Function keys
       [xK_space    .. xK_asciitilde]   -- ASCII and such

Now we're ready to define a list of keybindings:

fallbackKeys = [((mask, key), lockScreen)
               | mask <- masks
               , key  <- keys ]

This will generate a keybind definition for every possible combination of given masks and keys.

Now we have to apply these definitions to your config.

XMonad.Util.EZConfig has a very useful additionalKeys combinator that, given a config and a list of key binding definitions, will add the latter to the former, overwriting existing definitions if there's any clash. What we want is exactly the opposite: we want to apply fallbackKeys in such a way that they fill only undefined places, leaving already defined keybinds as they are. To achieve that, we'll define another function:

import qualified Data.Map as M

backupKeys :: XConfig a -> [((ButtonMask, KeySym), X ())] -> XConfig a
backupKeys conf keyList =
    conf { keys = \cnf -> M.union (keys conf cnf) (M.fromList keyList) }

This is a direct copy-paste of additionalKeys, but the arguments to Data.Map.union there are swapped, resulting in the behaviour we want. (See union's documentation to understand how this works.)

And now we can finally use all that. Somewhere in your xmonad.hs you have a line like this:

main = xmonad $ def {…} `additionalKeys` myKeys

Amend it as follows:

main = xmonad $ def {…} `additionalKeys` myKeys `backupKeys` fallbackKeys

Recompile and restart XMonad, and enjoy your new settings!

Related Question