    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Actions.OnScreen
    -- Copyright   :  (c) 2009 Nils Schweinsberg
    -- License     :  BSD3-style (see LICENSE)
    --
    -- Maintainer  :  Nils Schweinsberg <mail@n-sch.de>
    -- Stability   :  unstable
    -- Portability :  unportable
    --
    -- Control workspaces on different screens (in xinerama mode).
    --
    -----------------------------------------------------------------------------

    module XMonad.Actions.OnScreen (
        -- * Usage
        -- $usage
          onScreen
        , onScreen'
        , Focus(..)
        , viewOnScreen
        , greedyViewOnScreen
        , onlyOnScreen
        , toggleOnScreen
        , toggleGreedyOnScreen
        ) where

    import XMonad
    import XMonad.StackSet hiding (new)

    import Control.Monad (guard)
    -- import Control.Monad.State.Class (gets)
    import Data.Maybe (fromMaybe)

    -- | Focus data definitions
    data Focus = FocusNew                       -- ^ always focus the new screen
               | FocusCurrent                   -- ^ always keep the focus on the current screen
               | FocusTag WorkspaceId           -- ^ always focus tag i on the new stack
               | FocusTagVisible WorkspaceId    -- ^ focus tag i only if workspace with tag i is visible on the old stack

    -- | Run any function that modifies the stack on a given screen. This function
    -- will also need to know which Screen to focus after the function has been
    -- run.
    onScreen :: (WindowSet -> WindowSet) -- ^ function to run
             -> Focus                    -- ^ what to do with the focus
             -> ScreenId                 -- ^ screen id
             -> WindowSet                -- ^ current stack
             -> WindowSet
    onScreen f foc sc st = fromMaybe st $ do
        ws <- lookupWorkspace sc st

        let fStack      = f $ view ws st

        return $ setFocus foc st fStack

    -- set focus for new stack
    setFocus :: Focus
             -> WindowSet -- ^ old stack
             -> WindowSet -- ^ new stack
             -> WindowSet
    setFocus FocusNew _ new             = new
    setFocus FocusCurrent old new        =
        case lookupWorkspace (screen $ current old) new of
             Nothing -> new
             Just i -> view i new
    setFocus (FocusTag i) _ new         = view i new
    setFocus (FocusTagVisible i) old new =
        if i `elem` map (tag . workspace) (visible old)
           then setFocus (FocusTag i) old new
           else setFocus FocusCurrent old new

    -- | A variation of @onScreen@ which will take any @X ()@ function and run it
    -- on the given screen.
    -- Warning: This function will change focus even if the function it's supposed
    -- to run doesn't succeed.
    onScreen' :: X ()       -- ^ X function to run
              -> Focus      -- ^ focus
              -> ScreenId   -- ^ screen id
              -> X ()
    onScreen' x foc sc = do
        st <- gets windowset
        case lookupWorkspace sc st of
             Nothing -> return ()
             Just ws -> do
                 windows $ view ws
                 x
                 windows $ setFocus foc st

    -- | Switch to workspace @i@ on screen @sc@. If @i@ is visible use @view@ to
    -- switch focus to the workspace @i@.
    viewOnScreen :: ScreenId    -- ^ screen id
                 -> WorkspaceId -- ^ index of the workspace
                 -> WindowSet   -- ^ current stack
                 -> WindowSet
    viewOnScreen sid i =
        onScreen (view i) (FocusTag i) sid

    -- | Switch to workspace @i@ on screen @sc@. If @i@ is visible use @greedyView@
    -- to switch the current workspace with workspace @i@.
    greedyViewOnScreen :: ScreenId    -- ^ screen id
                       -> WorkspaceId -- ^ index of the workspace
                       -> WindowSet   -- ^ current stack
                       -> WindowSet
    greedyViewOnScreen sid i =
        onScreen (greedyView i) (FocusTagVisible i) sid

    -- | Switch to workspace @i@ on screen @sc@. If @i@ is visible do nothing.
    onlyOnScreen :: ScreenId    -- ^ screen id
                 -> WorkspaceId -- ^ index of the workspace
                 -> WindowSet   -- ^ current stack
                 -> WindowSet
    onlyOnScreen sid i =
        onScreen (view i) FocusCurrent sid

    -- | @toggleOrView@ as in "XMonad.Actions.CycleWS" for @onScreen@ with view
    toggleOnScreen :: ScreenId    -- ^ screen id
                   -> WorkspaceId -- ^ index of the workspace
                   -> WindowSet   -- ^ current stack
                   -> WindowSet
    toggleOnScreen sid i =
        onScreen (toggleOrView' view i) FocusCurrent sid

    -- | @toggleOrView@ from "XMonad.Actions.CycleWS" for @onScreen@ with greedyView
    toggleGreedyOnScreen :: ScreenId    -- ^ screen id
                         -> WorkspaceId -- ^ index of the workspace
                         -> WindowSet   -- ^ current stack
                         -> WindowSet
    toggleGreedyOnScreen sid i =
        onScreen (toggleOrView' greedyView i) FocusCurrent sid

    -- a \"pure\" version of X.A.CycleWS.toggleOrDoSkip
    toggleOrView' :: (WorkspaceId -> WindowSet -> WindowSet)   -- ^ function to run
                  -> WorkspaceId                               -- ^ tag to look for
                  -> WindowSet                                 -- ^ current stackset
                  -> WindowSet
    toggleOrView' f i st = fromMaybe (f i st) $ do
        let st' = hidden st
        -- make sure we actually have to do something
        guard $ i == (tag . workspace $ current st)
        guard $ not (null st')
        -- finally, toggle!
        return $ f (tag . head $ st') st

    -- $usage
    --
    -- This module provides an easy way to control, what you see on other screens in
    -- xinerama mode without having to focus them. Put this into your
    -- @~\/.xmonad\/xmonad.hs@:
    --
    -- > import XMonad.Actions.OnScreen
    --
    -- Then add the appropriate keybindings, for example replace your current keys
    -- to switch the workspaces with this at the bottom of your keybindings:
    --
    -- >     ++
    -- >     [ ((m .|. modm, k), windows (f i))
    -- >       | (i, k) <- zip (workspaces conf) ([xK_1 .. xK_9] ++ [xK_0])
    -- >       , (f, m) <- [ (viewOnScreen 0, 0)
    -- >                   , (viewOnScreen 1, controlMask)
    -- >                   , (greedyView, controlMask .|. shiftMask) ]
    -- >     ]
    --
    -- This will provide you with the following keybindings:
    --
    --      * modkey + 1-0:
    --      Switch to workspace 1-0 on screen 0
    --
    --      * modkey + control + 1-0:
    --      Switch to workspace 1-0 on screen 1
    --
    --      * modkey + control + shift + 1-0:
    --      Default greedyView behaviour
    --
    --
    -- A more basic version inside the default keybindings would be:
    --
    -- >        , ((modm .|. controlMask, xK_1), windows (viewOnScreen 0 "1"))
    --
    -- where 0 is the first screen and \"1\" the workspace with the tag \"1\".
    --
    -- For detailed instructions on editing your key bindings, see
    -- "XMonad.Doc.Extending#Editing_key_bindings".