    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Actions.Commands
    -- Copyright   :  (c) David Glasser 2007
    -- License     :  BSD3
    --
    -- Maintainer  :  glasser@mit.edu
    -- Stability   :  stable
    -- Portability :  portable
    --
    -- Allows you to run internal xmonad commands (X () actions) using
    -- a dmenu menu in addition to key bindings.  Requires dmenu and
    -- the Dmenu XMonad.Actions module.
    --
    -----------------------------------------------------------------------------

    module XMonad.Actions.Commands (
                                 -- * Usage
                                 -- $usage
                                 commandMap,
                                 runCommand,
                                 runCommand',
                                 workspaceCommands,
                                 screenCommands,
                                 defaultCommands
                                  ) where

    import XMonad
    import XMonad.StackSet hiding (workspaces)
    import XMonad.Util.Dmenu (dmenu)

    import qualified Data.Map as M
    import System.Exit
    import Data.Maybe

    -- $usage
    --
    -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
    --
    -- >    import XMonad.Actions.Commands
    --
    -- Then add a keybinding to the runCommand action:
    --
    -- >    , ((modm .|. controlMask, xK_y), commands >>= runCommand)
    --
    -- and define the list of commands you want to use:
    --
    -- >    commands :: X [(String, X ())]
    -- >    commands = defaultCommands
    --
    -- Whatever key you bound to will now cause a popup menu of internal
    -- xmonad commands to appear.  You can change the commands by changing
    -- the contents of the list returned by 'commands'.  (If you like it
    -- enough, you may even want to get rid of many of your other key
    -- bindings!)
    --
    -- For detailed instructions on editing your key bindings, see
    -- "XMonad.Doc.Extending#Editing_key_bindings".

    -- | Create a 'Data.Map.Map' from @String@s to xmonad actions from a
    --   list of pairs.
    commandMap :: [(String, X ())] -> M.Map String (X ())
    commandMap c = M.fromList c

    -- | Generate a list of commands to switch to\/send windows to workspaces.
    workspaceCommands :: X [(String, X ())]
    workspaceCommands = asks (workspaces . config) >>= \spaces -> return
                                [((m ++ show i), windows $ f i)
                                    | i <- spaces
                                    , (f, m) <- [(view, "view"), (shift, "shift")] ]

    -- | Generate a list of commands dealing with multiple screens.
    screenCommands :: [(String, X ())]
    screenCommands = [((m ++ show sc), screenWorkspace (fromIntegral sc) >>= flip whenJust (windows . f))
                          | sc <- [0, 1]::[Int] -- TODO: adapt to screen changes
                          , (f, m) <- [(view, "screen"), (shift, "screen-to-")]
                     ]

    -- | A nice pre-defined list of commands.
    defaultCommands :: X [(String, X ())]
    defaultCommands = do
        wscmds <- workspaceCommands
        return $ wscmds ++ screenCommands ++ otherCommands
     where
        otherCommands =
            [ ("shrink"              , sendMessage Shrink                               )
            , ("expand"              , sendMessage Expand                               )
            , ("next-layout"         , sendMessage NextLayout                           )
            , ("default-layout"      , asks (layoutHook . config) >>= setLayout         )
            , ("restart-wm"          , restart "xmonad" True                            )
            , ("restart-wm-no-resume", restart "xmonad" False                           )
            , ("xterm"               , spawn =<< asks (terminal .  config)              )
            , ("run"                 , spawn "exe=`dmenu_path | dmenu -b` && exec $exe" )
            , ("kill"                , kill                                             )
            , ("refresh"             , refresh                                          )
            , ("focus-up"            , windows focusUp                                  )
            , ("focus-down"          , windows focusDown                                )
            , ("swap-up"             , windows swapUp                                   )
            , ("swap-down"           , windows swapDown                                 )
            , ("swap-master"         , windows swapMaster                               )
            , ("sink"                , withFocused $ windows . sink                     )
            , ("quit-wm"             , io $ exitWith ExitSuccess                        )
            ]

    -- | Given a list of command\/action pairs, prompt the user to choose a
    --   command and return the corresponding action.
    runCommand :: [(String, X ())] -> X ()
    runCommand cl = do
      let m = commandMap cl
      choice <- dmenu (M.keys m)
      fromMaybe (return ()) (M.lookup choice m)

    -- | Given the name of a command from 'defaultCommands', return the
    --   corresponding action (or the null action if the command is not
    --   found).
    runCommand' :: String -> X ()
    runCommand' c = do
      m <- fmap commandMap defaultCommands
      fromMaybe (return ()) (M.lookup c m)