content: --------------------------------------------------------------------
  -- |
  -- Module      :  XMonad.Util.EZConfig
  -- Copyright   :  Devin Mullins <me@twifkak.com>
  --                Brent Yorgey <byorgey@gmail.com> (key parsing)
  -- License     :  BSD3-style (see LICENSE)
  --
  -- Maintainer  :  Devin Mullins <me@twifkak.com>
  --
  -- Useful helper functions for amending the default configuration, and for
  -- parsing keybindings specified in a special (emacs-like) format.
  --
  -- (See also "XMonad.Util.CustomKeys" in xmonad-contrib.)
  --
  --------------------------------------------------------------------
  
  module XMonad.Util.EZConfig (
                               -- * Usage
                               -- $usage
  
                               -- * Adding or removing keybindings
  
                               additionalKeys, additionalKeysP,
                               removeKeys, removeKeysP,
                               additionalMouseBindings, removeMouseBindings,
  
                               -- * Emacs-style keybinding specifications
  
                               mkKeymap, checkKeymap,
                               mkNamedKeymap,
  
                               parseKey -- used by XMonad.Util.Paste
                              ) where
  
  import XMonad
  import XMonad.Actions.Submap
  
  import XMonad.Util.NamedActions
  
  import qualified Data.Map as M
  import Data.List (foldl', sortBy, groupBy, nub)
  import Data.Ord (comparing)
  import Data.Maybe
  import Control.Arrow (first, (&&&))
  
  import Text.ParserCombinators.ReadP
  
  -- $usage
  -- To use this module, first import it into your @~\/.xmonad\/xmonad.hs@:
  --
  -- > import XMonad.Util.EZConfig
  --
  -- Then, use one of the provided functions to modify your
  -- configuration.  You can use 'additionalKeys', 'removeKeys',
  -- 'additionalMouseBindings', and 'removeMouseBindings' to easily add
  -- and remove keybindings or mouse bindings.  You can use 'mkKeymap'
  -- to create a keymap using emacs-style keybinding specifications
  -- like @\"M-x\"@ instead of @(modMask, xK_x)@, or 'additionalKeysP'
  -- and 'removeKeysP' to easily add or remove emacs-style keybindings.
  -- If you use emacs-style keybindings, the 'checkKeymap' function is
  -- provided, suitable for adding to your 'startupHook', which can warn
  -- you of any parse errors or duplicate bindings in your keymap.
  --
  -- For more information and usage examples, see the documentation
  -- provided with each exported function, and check the xmonad config
  -- archive (<http://haskell.org/haskellwiki/Xmonad/Config_archive>)
  -- for some real examples of use.
  
  -- |
  -- Add or override keybindings from the existing set. Example use:
  --
  -- > main = xmonad $ def { terminal = "urxvt" }
  -- >                 `additionalKeys`
  -- >                 [ ((mod1Mask, xK_m        ), spawn "echo 'Hi, mom!' | dzen2 -p 4")
  -- >                 , ((mod1Mask, xK_BackSpace), withFocused hide) -- N.B. this is an absurd thing to do
  -- >                 ]
  --
  -- This overrides the previous definition of mod-m.
  --
  -- Note that, unlike in xmonad 0.4 and previous, you can't use modMask to refer
  -- to the modMask you configured earlier. You must specify mod1Mask (or
  -- whichever), or add your own @myModMask = mod1Mask@ line.
  additionalKeys :: XConfig a -> [((ButtonMask, KeySym), X ())] -> XConfig a
  additionalKeys conf keyList =
      conf { keys = \cnf -> M.union (M.fromList keyList) (keys conf cnf) }
  
  -- | Like 'additionalKeys', except using short @String@ key
  --   descriptors like @\"M-m\"@ instead of @(modMask, xK_m)@, as
  --   described in the documentation for 'mkKeymap'.  For example:
  --
  -- > main = xmonad $ def { terminal = "urxvt" }
  -- >                 `additionalKeysP`
  -- >                 [ ("M-m", spawn "echo 'Hi, mom!' | dzen2 -p 4")
  -- >                 , ("M-<Backspace>", withFocused hide) -- N.B. this is an absurd thing to do
  -- >                 ]
  
  additionalKeysP :: XConfig l -> [(String, X ())] -> XConfig l
  additionalKeysP conf keyList =
      conf { keys = \cnf -> M.union (mkKeymap cnf keyList) (keys conf cnf) }
  
  -- |
  -- Remove standard keybindings you're not using. Example use:
  --
  -- > main = xmonad $ def { terminal = "urxvt" }
  -- >                 `removeKeys` [(mod1Mask .|. shiftMask, n) | n <- [xK_1 .. xK_9]]
  removeKeys :: XConfig a -> [(ButtonMask, KeySym)] -> XConfig a
  removeKeys conf keyList =
      conf { keys = \cnf -> keys conf cnf `M.difference` M.fromList (zip keyList $ repeat ()) }
  
  -- | Like 'removeKeys', except using short @String@ key descriptors
  --   like @\"M-m\"@ instead of @(modMask, xK_m)@, as described in the
  --   documentation for 'mkKeymap'. For example:
  --
  -- > main = xmonad $ def { terminal = "urxvt" }
  -- >                 `removeKeysP` ["M-S-" ++ [n] | n <- ['1'..'9']]
  
  removeKeysP :: XConfig l -> [String] -> XConfig l
  removeKeysP conf keyList =
      conf { keys = \cnf -> keys conf cnf `M.difference` mkKeymap cnf (zip keyList $ repeat (return ())) }
  
  -- | Like 'additionalKeys', but for mouse bindings.
  additionalMouseBindings :: XConfig a -> [((ButtonMask, Button), Window -> X ())] -> XConfig a
  additionalMouseBindings conf mouseBindingsList =
      conf { mouseBindings = \cnf -> M.union (M.fromList mouseBindingsList) (mouseBindings conf cnf) }
  
  -- | Like 'removeKeys', but for mouse bindings.
  removeMouseBindings :: XConfig a -> [(ButtonMask, Button)] -> XConfig a
  removeMouseBindings conf mouseBindingList =
      conf { mouseBindings = \cnf -> mouseBindings conf cnf `M.difference`
                                     M.fromList (zip mouseBindingList $ repeat ()) }
  
  
  --------------------------------------------------------------
  --  Keybinding parsing  ---------------------------------------
  --------------------------------------------------------------
  
  -- | Given a config (used to determine the proper modifier key to use)
  --   and a list of @(String, X ())@ pairs, create a key map by parsing
  --   the key sequence descriptions contained in the Strings.  The key
  --   sequence descriptions are \"emacs-style\": @M-@, @C-@, @S-@, and
  --   @M\#-@ denote mod, control, shift, and mod1-mod5 (where @\#@ is
  --   replaced by the appropriate number) respectively.  Note that if
  --   you want to make a keybinding using \'alt\' even though you use a
  --   different key (like the \'windows\' key) for \'mod\', you can use
  --   something like @\"M1-x\"@ for alt+x (check the output of @xmodmap@
  --   to see which mod key \'alt\' is bound to). Some spe[ial keys can
  --   also be specified by enclosing their name in angle brackets.
  --
  --   For example, @\"M-C-x\"@ denotes mod+ctrl+x; @\"S-\<Escape\>\"@
  --   denotes shift-escape; @\"M1-C-\<Delete\>\"@ denotes alt+ctrl+delete
  --   (assuming alt is bound to mod1, which is common).
  --
  --   Sequences of keys can also be specified by separating the key
  --   descriptions with spaces. For example, @\"M-x y \<Down\>\"@ denotes the
  --   sequence of keys mod+x, y, down.  Submaps (see
  --   "XMonad.Actions.Submap") will be automatically generated to
  --   correctly handle these cases.
  --
  --   So, for example, a complete key map might be specified as
  --
  -- > keys = \c -> mkKeymap c $
  -- >     [ ("M-S-<Return>", spawn $ terminal c)
  -- >     , ("M-x w", spawn "xmessage 'woohoo!'")  -- type mod+x then w to pop up 'woohoo!'
  -- >     , ("M-x y", spawn "xmessage 'yay!'")     -- type mod+x then y to pop up 'yay!'
  -- >     , ("M-S-c", kill)
  -- >     ]
  --
  -- Alternatively, you can use 'additionalKeysP' to automatically
  -- create a keymap and add it to your config.
  --
  -- Here is a complete list of supported special keys.  Note that a few
  -- keys, such as the arrow keys, have synonyms.  If there are other
  -- special keys you would like to see supported, feel free to submit a
  -- patch, or ask on the xmonad mailing list; adding special keys is
  -- quite simple.
  --
  -- > <Backspace>
  -- > <Tab>
  -- > <Return>
  -- > <Pause>
  -- > <Scroll_lock>
  -- > <Sys_Req>
  -- > <Print>
  -- > <Escape>, <Esc>
  -- > <Delete>
  -- > <Home>
  -- > <Left>, <L>
  -- > <Up>, <U>
  -- > <Right>, <R>
  -- > <Down>, <D>
  -- > <Page_Up>
  -- > <Page_Down>
  -- > <End>
  -- > <Insert>
  -- > <Break>
  -- > <Space>
  -- > <F1>-<F24>
  -- > <KP_Space>
  -- > <KP_Tab>
  -- > <KP_Enter>
  -- > <KP_F1>
  -- > <KP_F2>
  -- > <KP_F3>
  -- > <KP_F4>
  -- > <KP_Home>
  -- > <KP_Left>
  -- > <KP_Up>
  -- > <KP_Right>
  -- > <KP_Down>
  -- > <KP_Prior>
  -- > <KP_Page_Up>
  -- > <KP_Next>
  -- > <KP_Page_Down>
  -- > <KP_End>
  -- > <KP_Begin>
  -- > <KP_Insert>
  -- > <KP_Delete>
  -- > <KP_Equal>
  -- > <KP_Multiply>
  -- > <KP_Add>
  -- > <KP_Separator>
  -- > <KP_Subtract>
  -- > <KP_Decimal>
  -- > <KP_Divide>
  -- > <KP_0>-<KP_9>
  --
  -- Long list of multimedia keys. Please note that not all keys may be
  -- present in your particular setup although most likely they will do.
  --
  -- > <XF86ModeLock>
  -- > <XF86MonBrightnessUp>
  -- > <XF86MonBrightnessDown>
  -- > <XF86KbdLightOnOff>
  -- > <XF86KbdBrightnessUp>
  -- > <XF86KbdBrightnessDown>
  -- > <XF86Standby>
  -- > <XF86AudioLowerVolume>
  -- > <XF86AudioMute>
  -- > <XF86AudioRaiseVolume>
  -- > <XF86AudioPlay>
  -- > <XF86AudioStop>
  -- > <XF86AudioPrev>
  -- > <XF86AudioNext>
  -- > <XF86HomePage>
  -- > <XF86Mail>
  -- > <XF86Start>
  -- > <XF86Search>
  -- > <XF86AudioRecord>
  -- > <XF86Calculator>
  -- > <XF86Memo>
  -- > <XF86ToDoList>
  -- > <XF86Calendar>
  -- > <XF86PowerDown>
  -- > <XF86ContrastAdjust>
  -- > <XF86RockerUp>
  -- > <XF86RockerDown>
  -- > <XF86RockerEnter>
  -- > <XF86Back>
  -- > <XF86Forward>
  -- > <XF86Stop>
  -- > <XF86Refresh>
  -- > <XF86PowerOff>
  -- > <XF86WakeUp>
  -- > <XF86Eject>
  -- > <XF86ScreenSaver>
  -- > <XF86WWW>
  -- > <XF86Sleep>
  -- > <XF86Favorites>
  -- > <XF86AudioPause>
  -- > <XF86AudioMedia>
  -- > <XF86MyComputer>
  -- > <XF86VendorHome>
  -- > <XF86LightBulb>
  -- > <XF86Shop>
  -- > <XF86History>
  -- > <XF86OpenURL>
  -- > <XF86AddFavorite>
  -- > <XF86HotLinks>
  -- > <XF86BrightnessAdjust>
  -- > <XF86Finance>
  -- > <XF86Community>
  -- > <XF86AudioRewind>
  -- > <XF86XF86BackForward>
  -- > <XF86Launch0>-<XF86Launch9>, <XF86LaunchA>-<XF86LaunchF>
  -- > <XF86ApplicationLeft>
  -- > <XF86ApplicationRight>
  -- > <XF86Book>
  -- > <XF86CD>
  -- > <XF86Calculater>
  -- > <XF86Clear>
  -- > <XF86Close>
  -- > <XF86Copy>
  -- > <XF86Cut>
  -- > <XF86Display>
  -- > <XF86DOS>
  -- > <XF86Documents>
  -- > <XF86Excel>
  -- > <XF86Explorer>
  -- > <XF86Game>
  -- > <XF86Go>
  -- > <XF86iTouch>
  -- > <XF86LogOff>
  -- > <XF86Market>
  -- > <XF86Meeting>
  -- > <XF86MenuKB>
  -- > <XF86MenuPB>
  -- > <XF86MySites>
  -- > <XF86New>
  -- > <XF86News>
  -- > <XF86OfficeHome>
  -- > <XF86Open>
  -- > <XF86Option>
  -- > <XF86Paste>
  -- > <XF86Phone>
  -- > <XF86Q>
  -- > <XF86Reply>
  -- > <XF86Reload>
  -- > <XF86RotateWindows>
  -- > <XF86RotationPB>
  -- > <XF86RotationKB>
  -- > <XF86Save>
  -- > <XF86ScrollUp>
  -- > <XF86ScrollDown>
  -- > <XF86ScrollClick>
  -- > <XF86Send>
  -- > <XF86Spell>
  -- > <XF86SplitScreen>
  -- > <XF86Support>
  -- > <XF86TaskPane>
  -- > <XF86Terminal>
  -- > <XF86Tools>
  -- > <XF86Travel>
  -- > <XF86UserPB>
  -- > <XF86User1KB>
  -- > <XF86User2KB>
  -- > <XF86Video>
  -- > <XF86WheelButton>
  -- > <XF86Word>
  -- > <XF86Xfer>
  -- > <XF86ZoomIn>
  -- > <XF86ZoomOut>
  -- > <XF86Away>
  -- > <XF86Messenger>
  -- > <XF86WebCam>
  -- > <XF86MailForward>
  -- > <XF86Pictures>
  -- > <XF86Music>
  -- > <XF86TouchpadToggle>
  -- > <XF86AudioMicMute>
  -- > <XF86_Switch_VT_1>-<XF86_Switch_VT_12>
  -- > <XF86_Ungrab>
  -- > <XF86_ClearGrab>
  -- > <XF86_Next_VMode>
  -- > <XF86_Prev_VMode>
  
  mkKeymap :: XConfig l -> [(String, X ())] -> M.Map (KeyMask, KeySym) (X ())
  mkKeymap c = M.fromList . mkSubmaps . readKeymap c
  
  mkNamedKeymap :: XConfig l -> [(String, NamedAction)] -> [((KeyMask, KeySym), NamedAction)]
  mkNamedKeymap c = mkNamedSubmaps . readKeymap c
  
  -- | Given a list of pairs of parsed key sequences and actions,
  --   group them into submaps in the appropriate way.
  
  mkNamedSubmaps :: [([(KeyMask, KeySym)], NamedAction)] -> [((KeyMask, KeySym), NamedAction)]
  mkNamedSubmaps = mkSubmaps' submapName
  
  mkSubmaps :: [ ([(KeyMask,KeySym)], X ()) ] -> [((KeyMask, KeySym), X ())]
  mkSubmaps = mkSubmaps' $ submap . M.fromList
  
  mkSubmaps' ::  (Ord a) => ([(a, c)] -> c) -> [([a], c)] -> [(a, c)]
  mkSubmaps' subm binds = map combine gathered
    where gathered = groupBy fstKey
                   . sortBy (comparing fst)
                   $ binds
          combine [([k],act)] = (k,act)
          combine ks = (head . fst . head $ ks,
                        subm . mkSubmaps' subm $ map (first tail) ks)
          fstKey = (==) `on` (head . fst)
  
  on :: (a -> a -> b) -> (c -> a) -> c -> c -> b
  op `on` f = \x y -> f x `op` f y
  
  -- | Given a configuration record and a list of (key sequence
  --   description, action) pairs, parse the key sequences into lists of
  --   @(KeyMask,KeySym)@ pairs.  Key sequences which fail to parse will
  --   be ignored.
  readKeymap :: XConfig l -> [(String, t)] -> [([(KeyMask, KeySym)], t)]
  readKeymap c = mapMaybe (maybeKeys . first (readKeySequence c))
    where maybeKeys (Nothing,_) = Nothing
          maybeKeys (Just k, act) = Just (k, act)
  
  -- | Parse a sequence of keys, returning Nothing if there is
  --   a parse failure (no parse, or ambiguous parse).
  readKeySequence :: XConfig l -> String -> Maybe [(KeyMask, KeySym)]
  readKeySequence c = listToMaybe . parses
    where parses = map fst . filter (null.snd) . readP_to_S (parseKeySequence c)
  
  -- | Parse a sequence of key combinations separated by spaces, e.g.
  --   @\"M-c x C-S-2\"@ (mod+c, x, ctrl+shift+2).
  parseKeySequence :: XConfig l -> ReadP [(KeyMask, KeySym)]
  parseKeySequence c = sepBy1 (parseKeyCombo c) (many1 $ char ' ')
  
  -- | Parse a modifier-key combination such as "M-C-s" (mod+ctrl+s).
  parseKeyCombo :: XConfig l -> ReadP (KeyMask, KeySym)
  parseKeyCombo c = do mods <- many (parseModifier c)
                       k <- parseKey
                       return (foldl' (.|.) 0 mods, k)
  
  -- | Parse a modifier: either M- (user-defined mod-key),
  --   C- (control), S- (shift), or M#- where # is an integer
  --   from 1 to 5 (mod1Mask through mod5Mask).
  parseModifier :: XConfig l -> ReadP KeyMask
  parseModifier c =  (string "M-" >> return (modMask c))
                 +++ (string "C-" >> return controlMask)
                 +++ (string "S-" >> return shiftMask)
                 +++ do _ <- char 'M'
                        n <- satisfy (`elem` ['1'..'5'])
                        _ <- char '-'
                        return $ indexMod (read [n] - 1)
      where indexMod = (!!) [mod1Mask,mod2Mask,mod3Mask,mod4Mask,mod5Mask]
  
  -- | Parse an unmodified basic key, like @\"x\"@, @\"<F1>\"@, etc.
  parseKey :: ReadP KeySym
  parseKey = parseRegular +++ parseSpecial
  
  -- | Parse a regular key name (represented by itself).
  parseRegular :: ReadP KeySym
  parseRegular = choice [ char s >> return k
                        | (s,k) <- zip ['!'..'~'] [xK_exclam..xK_asciitilde]
                        ]
  
  -- | Parse a special key name (one enclosed in angle brackets).
  parseSpecial :: ReadP KeySym
  parseSpecial = do _   <- char '<'
                    key <- choice [ string name >> return k
                                  | (name,k) <- keyNames
                                  ]
                    _   <- char '>'
                    return key
  
  -- | A list of all special key names and their associated KeySyms.
  keyNames :: [(String, KeySym)]
  keyNames = functionKeys ++ specialKeys ++ multimediaKeys
  
  -- | A list pairing function key descriptor strings (e.g. @\"<F2>\"@) with
  --   the associated KeySyms.
  functionKeys :: [(String, KeySym)]
  functionKeys = [ ('F' : show n, k)
                 | (n,k) <- zip ([1..24] :: [Int]) [xK_F1..] ]
  
  -- | A list of special key names and their corresponding KeySyms.
  specialKeys :: [(String, KeySym)]
  specialKeys = [ ("Backspace"  , xK_BackSpace)
                , ("Tab"        , xK_Tab)
                , ("Return"     , xK_Return)
                , ("Pause"      , xK_Pause)
                , ("Scroll_lock", xK_Scroll_Lock)
                , ("Sys_Req"    , xK_Sys_Req)
                , ("Print"      , xK_Print)
                , ("Escape"     , xK_Escape)
                , ("Esc"        , xK_Escape)
                , ("Delete"     , xK_Delete)
                , ("Home"       , xK_Home)
                , ("Left"       , xK_Left)
                , ("Up"         , xK_Up)
                , ("Right"      , xK_Right)
                , ("Down"       , xK_Down)
                , ("L"          , xK_Left)
                , ("U"          , xK_Up)
                , ("R"          , xK_Right)
                , ("D"          , xK_Down)
                , ("Page_Up"    , xK_Page_Up)
                , ("Page_Down"  , xK_Page_Down)
                , ("End"        , xK_End)
                , ("Insert"     , xK_Insert)
                , ("Break"      , xK_Break)
                , ("Space"      , xK_space)
                , ("KP_Space"   , xK_KP_Space)
                , ("KP_Tab"     , xK_KP_Tab)
                , ("KP_Enter"   , xK_KP_Enter)
                , ("KP_F1"      , xK_KP_F1)
                , ("KP_F2"      , xK_KP_F2)
                , ("KP_F3"      , xK_KP_F3)
                , ("KP_F4"      , xK_KP_F4)
                , ("KP_Home"    , xK_KP_Home)
                , ("KP_Left"    , xK_KP_Left)
                , ("KP_Up"      , xK_KP_Up)
                , ("KP_Right"   , xK_KP_Right)
                , ("KP_Down"    , xK_KP_Down)
                , ("KP_Prior"   , xK_KP_Prior)
                , ("KP_Page_Up" , xK_KP_Page_Up)
                , ("KP_Next"    , xK_KP_Next)
                , ("KP_Page_Down", xK_KP_Page_Down)
                , ("KP_End"     , xK_KP_End)
                , ("KP_Begin"   , xK_KP_Begin)
                , ("KP_Insert"  , xK_KP_Insert)
                , ("KP_Delete"  , xK_KP_Delete)
                , ("KP_Equal"   , xK_KP_Equal)
                , ("KP_Multiply", xK_KP_Multiply)
                , ("KP_Add"     , xK_KP_Add)
                , ("KP_Separator", xK_KP_Separator)
                , ("KP_Subtract", xK_KP_Subtract)
                , ("KP_Decimal" , xK_KP_Decimal)
                , ("KP_Divide"  , xK_KP_Divide)
                , ("KP_0"       , xK_KP_0)
                , ("KP_1"       , xK_KP_1)
                , ("KP_2"       , xK_KP_2)
                , ("KP_3"       , xK_KP_3)
                , ("KP_4"       , xK_KP_4)
                , ("KP_5"       , xK_KP_5)
                , ("KP_6"       , xK_KP_6)
                , ("KP_7"       , xK_KP_7)
                , ("KP_8"       , xK_KP_8)
                , ("KP_9"       , xK_KP_9)
                ]
  
  -- | List of multimedia keys. If X server does not know about some
  -- | keysym it's omitted from list. (stringToKeysym returns noSymbol in this case)
  multimediaKeys :: [(String, KeySym)]
  multimediaKeys = filter ((/= noSymbol) . snd) . map (id &&& stringToKeysym) $
                   [ "XF86ModeLock"
                   , "XF86MonBrightnessUp"
                   , "XF86MonBrightnessDown"
                   , "XF86KbdLightOnOff"
                   , "XF86KbdBrightnessUp"
                   , "XF86KbdBrightnessDown"
                   , "XF86Standby"
                   , "XF86AudioLowerVolume"
                   , "XF86AudioMute"
                   , "XF86AudioRaiseVolume"
                   , "XF86AudioPlay"
                   , "XF86AudioStop"
                   , "XF86AudioPrev"
                   , "XF86AudioNext"
                   , "XF86HomePage"
                   , "XF86Mail"
                   , "XF86Start"
                   , "XF86Search"
                   , "XF86AudioRecord"
                   , "XF86Calculator"
                   , "XF86Memo"
                   , "XF86ToDoList"
                   , "XF86Calendar"
                   , "XF86PowerDown"
                   , "XF86ContrastAdjust"
                   , "XF86RockerUp"
                   , "XF86RockerDown"
                   , "XF86RockerEnter"
                   , "XF86Back"
                   , "XF86Forward"
                   , "XF86Stop"
                   , "XF86Refresh"
                   , "XF86PowerOff"
                   , "XF86WakeUp"
                   , "XF86Eject"
                   , "XF86ScreenSaver"
                   , "XF86WWW"
                   , "XF86Sleep"
                   , "XF86Favorites"
                   , "XF86AudioPause"
                   , "XF86AudioMedia"
                   , "XF86MyComputer"
                   , "XF86VendorHome"
                   , "XF86LightBulb"
                   , "XF86Shop"
                   , "XF86History"
                   , "XF86OpenURL"
                   , "XF86AddFavorite"
                   , "XF86HotLinks"
                   , "XF86BrightnessAdjust"
                   , "XF86Finance"
                   , "XF86Community"
                   , "XF86AudioRewind"
                   , "XF86BackForward"
                   , "XF86Launch0"
                   , "XF86Launch1"
                   , "XF86Launch2"
                   , "XF86Launch3"
                   , "XF86Launch4"
                   , "XF86Launch5"
                   , "XF86Launch6"
                   , "XF86Launch7"
                   , "XF86Launch8"
                   , "XF86Launch9"
                   , "XF86LaunchA"
                   , "XF86LaunchB"
                   , "XF86LaunchC"
                   , "XF86LaunchD"
                   , "XF86LaunchE"
                   , "XF86LaunchF"
                   , "XF86ApplicationLeft"
                   , "XF86ApplicationRight"
                   , "XF86Book"
                   , "XF86CD"
                   , "XF86Calculater"
                   , "XF86Clear"
                   , "XF86Close"
                   , "XF86Copy"
                   , "XF86Cut"
                   , "XF86Display"
                   , "XF86DOS"
                   , "XF86Documents"
                   , "XF86Excel"
                   , "XF86Explorer"
                   , "XF86Game"
                   , "XF86Go"
                   , "XF86iTouch"
                   , "XF86LogOff"
                   , "XF86Market"
                   , "XF86Meeting"
                   , "XF86MenuKB"
                   , "XF86MenuPB"
                   , "XF86MySites"
                   , "XF86New"
                   , "XF86News"
                   , "XF86OfficeHome"
                   , "XF86Open"
                   , "XF86Option"
                   , "XF86Paste"
                   , "XF86Phone"
                   , "XF86Q"
                   , "XF86Reply"
                   , "XF86Reload"
                   , "XF86RotateWindows"
                   , "XF86RotationPB"
                   , "XF86RotationKB"
                   , "XF86Save"
                   , "XF86ScrollUp"
                   , "XF86ScrollDown"
                   , "XF86ScrollClick"
                   , "XF86Send"
                   , "XF86Spell"
                   , "XF86SplitScreen"
                   , "XF86Support"
                   , "XF86TaskPane"
                   , "XF86Terminal"
                   , "XF86Tools"
                   , "XF86Travel"
                   , "XF86UserPB"
                   , "XF86User1KB"
                   , "XF86User2KB"
                   , "XF86Video"
                   , "XF86WheelButton"
                   , "XF86Word"
                   , "XF86Xfer"
                   , "XF86ZoomIn"
                   , "XF86ZoomOut"
                   , "XF86Away"
                   , "XF86Messenger"
                   , "XF86WebCam"
                   , "XF86MailForward"
                   , "XF86Pictures"
                   , "XF86Music"
                   , "XF86TouchpadToggle"
                   , "XF86AudioMicMute"
                   , "XF86_Switch_VT_1"
                   , "XF86_Switch_VT_2"
                   , "XF86_Switch_VT_3"
                   , "XF86_Switch_VT_4"
                   , "XF86_Switch_VT_5"
                   , "XF86_Switch_VT_6"
                   , "XF86_Switch_VT_7"
                   , "XF86_Switch_VT_8"
                   , "XF86_Switch_VT_9"
                   , "XF86_Switch_VT_10"
                   , "XF86_Switch_VT_11"
                   , "XF86_Switch_VT_12"
                   , "XF86_Ungrab"
                   , "XF86_ClearGrab"
                   , "XF86_Next_VMode"
                   , "XF86_Prev_VMode" ]
  
  -- | Given a configuration record and a list of (key sequence
  --   description, action) pairs, check the key sequence descriptions
  --   for validity, and warn the user (via a popup xmessage window) of
  --   any unparseable or duplicate key sequences.  This function is
  --   appropriate for adding to your @startupHook@, and you are highly
  --   encouraged to do so; otherwise, duplicate or unparseable
  --   keybindings will be silently ignored.
  --
  --   For example, you might do something like this:
  --
  -- > main = xmonad $ myConfig
  -- >
  -- > myKeymap = [("S-M-c", kill), ...]
  -- > myConfig = def {
  -- >     ...
  -- >     keys = \c -> mkKeymap c myKeymap
  -- >     startupHook = return () >> checkKeymap myConfig myKeymap
  -- >     ...
  -- > }
  --
  -- NOTE: the @return ()@ in the example above is very important!
  -- Otherwise, you might run into problems with infinite mutual
  -- recursion: the definition of myConfig depends on the definition of
  -- startupHook, which depends on the definition of myConfig, ... and
  -- so on.  Actually, it's likely that the above example in particular
  -- would be OK without the @return ()@, but making @myKeymap@ take
  -- @myConfig@ as a parameter would definitely lead to
  -- problems. Believe me.  It, uh, happened to my friend. In... a
  -- dream. Yeah. In any event, the @return () >>@ introduces enough
  -- laziness to break the deadlock.
  --
  checkKeymap :: XConfig l -> [(String, a)] -> X ()
  checkKeymap conf km = warn (doKeymapCheck conf km)
    where warn ([],[])   = return ()
          warn (bad,dup) = spawn $ "xmessage 'Warning:\n"
                              ++ msg "bad" bad ++ "\n"
                              ++ msg "duplicate" dup ++ "'"
          msg _ [] = ""
          msg m xs = m ++ " keybindings detected: " ++ showBindings xs
          showBindings = unwords . map (("\""++) . (++"\""))
  
  -- | Given a config and a list of (key sequence description, action)
  --   pairs, check the key sequence descriptions for validity,
  --   returning a list of unparseable key sequences, and a list of
  --   duplicate key sequences.
  doKeymapCheck :: XConfig l -> [(String,a)] -> ([String], [String])
  doKeymapCheck conf km = (bad,dups)
    where ks = map ((readKeySequence conf &&& id) . fst) km
          bad = nub . map snd . filter (isNothing . fst) $ ks
          dups = map (snd . head)
               . filter ((>1) . length)
               . groupBy ((==) `on` fst)
               . sortBy (comparing fst)
               . map (first fromJust)
               . filter (isJust . fst)
               $ ks
  