-----------------------------------------------------------------------------
-- |
-- 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
    , viewOnScreen
    , greedyViewOnScreen
    , onlyOnScreen
    ) where

import XMonad.StackSet
import Control.Monad(guard)
import Data.List
import Data.Maybe(fromMaybe)
import Data.Function(on)

-- $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".

-- | Switch to the (hidden) workspace with index 'i' on the screen 'sc'.
-- A default function (for example 'view' or 'greedyView') will be run if 'sc' is
-- the current screen, no valid screen id or workspace 'i' is already visible.
onScreen :: (Eq sid, Eq i)
          => (i -> StackSet i l a sid sd -> StackSet i l a sid sd) -- ^ default action
          -> sid                      -- ^ screen id
          -> i                        -- ^ index of the workspace
          -> StackSet i l a sid sd    -- ^ current stack
          -> StackSet i l a sid sd
onScreen defFunc sc i st = fromMaybe (defFunc i st) $ do
    -- on unfocused current screen
    guard $ screen (current st) /= sc
    x <- find ((i==) . tag    ) (hidden  st)
    s <- find ((sc==) . screen) (screens st)
    o <- find ((sc==) . screen) (visible st)
    let newScreen = s { workspace = x }
    return st { visible   = newScreen : deleteBy ((==) `on` screen) newScreen (visible st)
              , hidden    = workspace o : deleteBy ((==) `on` tag) x (hidden st)
              }

-- | Switch to workspace 'i' on screen 'sc'. If 'i' is visible use 'greedyView'
-- to switch the current workspace with workspace 'i'.
greedyViewOnScreen :: (Eq sid, Eq i)
    => sid                          -- ^ screen id
    -> i                            -- ^ index of the workspace
    -> StackSet i l a sid sd        -- ^ current stack
    -> StackSet i l a sid sd
greedyViewOnScreen = onScreen greedyView

-- | Switch to workspace 'i' on screen 'sc'. If 'i' is visible use 'view' to
-- switch focus to the workspace 'i'.
viewOnScreen :: (Eq sid, Eq i)
    => sid                          -- ^ screen id
    -> i                            -- ^ index of the workspace
    -> StackSet i l a sid sd        -- ^ current stack
    -> StackSet i l a sid sd
viewOnScreen = onScreen view

-- | Switch to workspace 'i' on screen 'sc'. If 'i' is visible do nothing.
onlyOnScreen :: (Eq sid, Eq i)
    => sid                          -- ^ screen id
    -> i                            -- ^ index of the workspace
    -> StackSet i l a sid sd        -- ^ current stack
    -> StackSet i l a sid sd
onlyOnScreen = onScreen doNothing
  where doNothing _ st = st