-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Actions.Commands
-- Description :  Run internal xmonad commands using a dmenu menu.
-- 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,
                             runCommandConfig,
                             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 XMonad.Prelude

-- $usage
--
-- You can use this module with the following in your @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
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.

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

-- | Generate a list of commands to switch to\/send windows to workspaces.
workspaceCommands :: X [(String, X ())]
workspaceCommands :: X [(WorkspaceId, X ())]
workspaceCommands = (XConf -> [WorkspaceId]) -> X [WorkspaceId]
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks (XConfig Layout -> [WorkspaceId]
forall (l :: * -> *). XConfig l -> [WorkspaceId]
workspaces (XConfig Layout -> [WorkspaceId])
-> (XConf -> XConfig Layout) -> XConf -> [WorkspaceId]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. XConf -> XConfig Layout
config) X [WorkspaceId]
-> ([WorkspaceId] -> X [(WorkspaceId, X ())])
-> X [(WorkspaceId, X ())]
forall a b. X a -> (a -> X b) -> X b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \[WorkspaceId]
spaces -> [(WorkspaceId, X ())] -> X [(WorkspaceId, X ())]
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return
                            [( WorkspaceId
m WorkspaceId -> WorkspaceId -> WorkspaceId
forall a. [a] -> [a] -> [a]
++ WorkspaceId -> WorkspaceId
forall a. Show a => a -> WorkspaceId
show WorkspaceId
i, (WindowSet -> WindowSet) -> X ()
windows ((WindowSet -> WindowSet) -> X ())
-> (WindowSet -> WindowSet) -> X ()
forall a b. (a -> b) -> a -> b
$ WorkspaceId -> WindowSet -> WindowSet
f WorkspaceId
i)
                                | WorkspaceId
i <- [WorkspaceId]
spaces
                                , (WorkspaceId -> WindowSet -> WindowSet
f, WorkspaceId
m) <- [(WorkspaceId -> WindowSet -> WindowSet
forall s i l a sd.
(Eq s, Eq i) =>
i -> StackSet i l a s sd -> StackSet i l a s sd
view, WorkspaceId
"view"), (WorkspaceId -> WindowSet -> WindowSet
forall a s i l sd.
(Ord a, Eq s, Eq i) =>
i -> StackSet i l a s sd -> StackSet i l a s sd
shift, WorkspaceId
"shift")] ]

-- | Generate a list of commands dealing with multiple screens.
screenCommands :: [(String, X ())]
screenCommands :: [(WorkspaceId, X ())]
screenCommands = [( WorkspaceId
m WorkspaceId -> WorkspaceId -> WorkspaceId
forall a. [a] -> [a] -> [a]
++ Int -> WorkspaceId
forall a. Show a => a -> WorkspaceId
show Int
sc, ScreenId -> X (Maybe WorkspaceId)
screenWorkspace (Int -> ScreenId
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
sc) X (Maybe WorkspaceId) -> (Maybe WorkspaceId -> X ()) -> X ()
forall a b. X a -> (a -> X b) -> X b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (Maybe WorkspaceId -> (WorkspaceId -> X ()) -> X ())
-> (WorkspaceId -> X ()) -> Maybe WorkspaceId -> X ()
forall a b c. (a -> b -> c) -> b -> a -> c
flip Maybe WorkspaceId -> (WorkspaceId -> X ()) -> X ()
forall (m :: * -> *) a. Monad m => Maybe a -> (a -> m ()) -> m ()
whenJust ((WindowSet -> WindowSet) -> X ()
windows ((WindowSet -> WindowSet) -> X ())
-> (WorkspaceId -> WindowSet -> WindowSet) -> WorkspaceId -> X ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WorkspaceId -> WindowSet -> WindowSet
f))
                      | Int
sc <- [Int
0, Int
1]::[Int] -- TODO: adapt to screen changes
                      , (WorkspaceId -> WindowSet -> WindowSet
f, WorkspaceId
m) <- [(WorkspaceId -> WindowSet -> WindowSet
forall s i l a sd.
(Eq s, Eq i) =>
i -> StackSet i l a s sd -> StackSet i l a s sd
view, WorkspaceId
"screen"), (WorkspaceId -> WindowSet -> WindowSet
forall a s i l sd.
(Ord a, Eq s, Eq i) =>
i -> StackSet i l a s sd -> StackSet i l a s sd
shift, WorkspaceId
"screen-to-")]
                 ]

-- | A nice pre-defined list of commands.
defaultCommands :: X [(String, X ())]
defaultCommands :: X [(WorkspaceId, X ())]
defaultCommands = do
    [(WorkspaceId, X ())]
wscmds <- X [(WorkspaceId, X ())]
workspaceCommands
    [(WorkspaceId, X ())] -> X [(WorkspaceId, X ())]
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return ([(WorkspaceId, X ())] -> X [(WorkspaceId, X ())])
-> [(WorkspaceId, X ())] -> X [(WorkspaceId, X ())]
forall a b. (a -> b) -> a -> b
$ [(WorkspaceId, X ())]
wscmds [(WorkspaceId, X ())]
-> [(WorkspaceId, X ())] -> [(WorkspaceId, X ())]
forall a. [a] -> [a] -> [a]
++ [(WorkspaceId, X ())]
screenCommands [(WorkspaceId, X ())]
-> [(WorkspaceId, X ())] -> [(WorkspaceId, X ())]
forall a. [a] -> [a] -> [a]
++ [(WorkspaceId, X ())]
otherCommands
 where
    otherCommands :: [(WorkspaceId, X ())]
otherCommands =
        [ (WorkspaceId
"shrink"              , Resize -> X ()
forall a. Message a => a -> X ()
sendMessage Resize
Shrink                               )
        , (WorkspaceId
"expand"              , Resize -> X ()
forall a. Message a => a -> X ()
sendMessage Resize
Expand                               )
        , (WorkspaceId
"next-layout"         , ChangeLayout -> X ()
forall a. Message a => a -> X ()
sendMessage ChangeLayout
NextLayout                           )
        , (WorkspaceId
"default-layout"      , (XConf -> Layout Window) -> X (Layout Window)
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks (XConfig Layout -> Layout Window
forall (l :: * -> *). XConfig l -> l Window
layoutHook (XConfig Layout -> Layout Window)
-> (XConf -> XConfig Layout) -> XConf -> Layout Window
forall b c a. (b -> c) -> (a -> b) -> a -> c
. XConf -> XConfig Layout
config) X (Layout Window) -> (Layout Window -> X ()) -> X ()
forall a b. X a -> (a -> X b) -> X b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Layout Window -> X ()
setLayout         )
        , (WorkspaceId
"restart-wm"          , WorkspaceId -> Bool -> X ()
restart WorkspaceId
"xmonad" Bool
True                            )
        , (WorkspaceId
"restart-wm-no-resume", WorkspaceId -> Bool -> X ()
restart WorkspaceId
"xmonad" Bool
False                           )
        , (WorkspaceId
"xterm"               , WorkspaceId -> X ()
forall (m :: * -> *). MonadIO m => WorkspaceId -> m ()
spawn (WorkspaceId -> X ()) -> X WorkspaceId -> X ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (XConf -> WorkspaceId) -> X WorkspaceId
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks (XConfig Layout -> WorkspaceId
forall (l :: * -> *). XConfig l -> WorkspaceId
terminal (XConfig Layout -> WorkspaceId)
-> (XConf -> XConfig Layout) -> XConf -> WorkspaceId
forall b c a. (b -> c) -> (a -> b) -> a -> c
.  XConf -> XConfig Layout
config)              )
        , (WorkspaceId
"run"                 , WorkspaceId -> X ()
forall (m :: * -> *). MonadIO m => WorkspaceId -> m ()
spawn WorkspaceId
"exe=`dmenu_path | dmenu -b` && exec $exe" )
        , (WorkspaceId
"kill"                , X ()
kill                                             )
        , (WorkspaceId
"refresh"             , X ()
refresh                                          )
        , (WorkspaceId
"focus-up"            , (WindowSet -> WindowSet) -> X ()
windows WindowSet -> WindowSet
forall i l a s sd. StackSet i l a s sd -> StackSet i l a s sd
focusUp                                  )
        , (WorkspaceId
"focus-down"          , (WindowSet -> WindowSet) -> X ()
windows WindowSet -> WindowSet
forall i l a s sd. StackSet i l a s sd -> StackSet i l a s sd
focusDown                                )
        , (WorkspaceId
"swap-up"             , (WindowSet -> WindowSet) -> X ()
windows WindowSet -> WindowSet
forall i l a s sd. StackSet i l a s sd -> StackSet i l a s sd
swapUp                                   )
        , (WorkspaceId
"swap-down"           , (WindowSet -> WindowSet) -> X ()
windows WindowSet -> WindowSet
forall i l a s sd. StackSet i l a s sd -> StackSet i l a s sd
swapDown                                 )
        , (WorkspaceId
"swap-master"         , (WindowSet -> WindowSet) -> X ()
windows WindowSet -> WindowSet
forall i l a s sd. StackSet i l a s sd -> StackSet i l a s sd
swapMaster                               )
        , (WorkspaceId
"sink"                , (Window -> X ()) -> X ()
withFocused ((Window -> X ()) -> X ()) -> (Window -> X ()) -> X ()
forall a b. (a -> b) -> a -> b
$ (WindowSet -> WindowSet) -> X ()
windows ((WindowSet -> WindowSet) -> X ())
-> (Window -> WindowSet -> WindowSet) -> Window -> X ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Window -> WindowSet -> WindowSet
forall a i l s sd.
Ord a =>
a -> StackSet i l a s sd -> StackSet i l a s sd
sink                     )
        , (WorkspaceId
"quit-wm"             , IO () -> X ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
io IO ()
forall a. IO a
exitSuccess                                   )
        ]

-- | Given a list of command\/action pairs, prompt the user to choose a
--   command using dmenu and return the corresponding action.
runCommand :: [(String, X ())] -> X ()
runCommand :: [(WorkspaceId, X ())] -> X ()
runCommand = ([WorkspaceId] -> X WorkspaceId) -> [(WorkspaceId, X ())] -> X ()
runCommandConfig [WorkspaceId] -> X WorkspaceId
forall (m :: * -> *). MonadIO m => [WorkspaceId] -> m WorkspaceId
dmenu


-- | Given a list of command\/action pairs, prompt the user to choose a
--   command using dmenu-compatible launcher and return the corresponding action.
--   See X.U.Dmenu for compatible launchers.
runCommandConfig :: ([String] -> X String) -> [(String, X ())] -> X()
runCommandConfig :: ([WorkspaceId] -> X WorkspaceId) -> [(WorkspaceId, X ())] -> X ()
runCommandConfig [WorkspaceId] -> X WorkspaceId
f [(WorkspaceId, X ())]
cl = do
  let m :: Map WorkspaceId (X ())
m = [(WorkspaceId, X ())] -> Map WorkspaceId (X ())
commandMap [(WorkspaceId, X ())]
cl
  WorkspaceId
choice <- [WorkspaceId] -> X WorkspaceId
f (Map WorkspaceId (X ()) -> [WorkspaceId]
forall k a. Map k a -> [k]
M.keys Map WorkspaceId (X ())
m)
  X () -> Maybe (X ()) -> X ()
forall a. a -> Maybe a -> a
fromMaybe (() -> X ()
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return ()) (WorkspaceId -> Map WorkspaceId (X ()) -> Maybe (X ())
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup WorkspaceId
choice Map WorkspaceId (X ())
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' :: WorkspaceId -> X ()
runCommand' WorkspaceId
c = do
  Map WorkspaceId (X ())
m <- ([(WorkspaceId, X ())] -> Map WorkspaceId (X ()))
-> X [(WorkspaceId, X ())] -> X (Map WorkspaceId (X ()))
forall a b. (a -> b) -> X a -> X b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [(WorkspaceId, X ())] -> Map WorkspaceId (X ())
commandMap X [(WorkspaceId, X ())]
defaultCommands
  X () -> Maybe (X ()) -> X ()
forall a. a -> Maybe a -> a
fromMaybe (() -> X ()
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return ()) (WorkspaceId -> Map WorkspaceId (X ()) -> Maybe (X ())
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup WorkspaceId
c Map WorkspaceId (X ())
m)