-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Actions.OnScreen
-- Description :  Control workspaces on different screens (in xinerama mode).
-- 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.Prelude (fromMaybe, guard, empty)
import XMonad.StackSet hiding (new)


-- | 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 :: (WindowSet -> WindowSet)
-> Focus -> ScreenId -> WindowSet -> WindowSet
onScreen WindowSet -> WindowSet
f Focus
foc ScreenId
sc WindowSet
st = WindowSet -> Maybe WindowSet -> WindowSet
forall a. a -> Maybe a -> a
fromMaybe WindowSet
st (Maybe WindowSet -> WindowSet) -> Maybe WindowSet -> WindowSet
forall a b. (a -> b) -> a -> b
$ do
    WorkspaceId
ws <- ScreenId -> WindowSet -> Maybe WorkspaceId
forall s i l a sd. Eq s => s -> StackSet i l a s sd -> Maybe i
lookupWorkspace ScreenId
sc WindowSet
st

    let fStack :: WindowSet
fStack      = WindowSet -> WindowSet
f (WindowSet -> WindowSet) -> WindowSet -> WindowSet
forall a b. (a -> b) -> a -> b
$ 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
ws WindowSet
st

    WindowSet -> Maybe WindowSet
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (WindowSet -> Maybe WindowSet) -> WindowSet -> Maybe WindowSet
forall a b. (a -> b) -> a -> b
$ Focus -> WindowSet -> WindowSet -> WindowSet
setFocus Focus
foc WindowSet
st WindowSet
fStack


-- set focus for new stack
setFocus :: Focus
         -> WindowSet -- ^ old stack
         -> WindowSet -- ^ new stack
         -> WindowSet
setFocus :: Focus -> WindowSet -> WindowSet -> WindowSet
setFocus Focus
FocusNew WindowSet
_ WindowSet
new             = WindowSet
new
setFocus Focus
FocusCurrent WindowSet
old WindowSet
new        =
    case ScreenId -> WindowSet -> Maybe WorkspaceId
forall s i l a sd. Eq s => s -> StackSet i l a s sd -> Maybe i
lookupWorkspace (Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
-> ScreenId
forall i l a sid sd. Screen i l a sid sd -> sid
screen (Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
 -> ScreenId)
-> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
-> ScreenId
forall a b. (a -> b) -> a -> b
$ WindowSet
-> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
current WindowSet
old) WindowSet
new of
         Maybe WorkspaceId
Nothing -> WindowSet
new
         Just WorkspaceId
i -> 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
i WindowSet
new
setFocus (FocusTag WorkspaceId
i) WindowSet
_ WindowSet
new         = 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
i WindowSet
new
setFocus (FocusTagVisible WorkspaceId
i) WindowSet
old WindowSet
new =
    if WorkspaceId
i WorkspaceId -> [WorkspaceId] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` (Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
 -> WorkspaceId)
-> [Screen
      WorkspaceId (Layout Window) Window ScreenId ScreenDetail]
-> [WorkspaceId]
forall a b. (a -> b) -> [a] -> [b]
map (Workspace WorkspaceId (Layout Window) Window -> WorkspaceId
forall i l a. Workspace i l a -> i
tag (Workspace WorkspaceId (Layout Window) Window -> WorkspaceId)
-> (Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
    -> Workspace WorkspaceId (Layout Window) Window)
-> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
-> WorkspaceId
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
-> Workspace WorkspaceId (Layout Window) Window
forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
workspace) (WindowSet
-> [Screen
      WorkspaceId (Layout Window) Window ScreenId ScreenDetail]
forall i l a sid sd. StackSet i l a sid sd -> [Screen i l a sid sd]
visible WindowSet
old)
       then Focus -> WindowSet -> WindowSet -> WindowSet
setFocus (WorkspaceId -> Focus
FocusTag WorkspaceId
i) WindowSet
old WindowSet
new
       else Focus -> WindowSet -> WindowSet -> WindowSet
setFocus Focus
FocusCurrent WindowSet
old WindowSet
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 () -> Focus -> ScreenId -> X ()
onScreen' X ()
x Focus
foc ScreenId
sc = do
    WindowSet
st <- (XState -> WindowSet) -> X WindowSet
forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets XState -> WindowSet
windowset
    case ScreenId -> WindowSet -> Maybe WorkspaceId
forall s i l a sd. Eq s => s -> StackSet i l a s sd -> Maybe i
lookupWorkspace ScreenId
sc WindowSet
st of
         Maybe WorkspaceId
Nothing -> () -> X ()
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
         Just WorkspaceId
ws -> do
             (WindowSet -> WindowSet) -> X ()
windows ((WindowSet -> WindowSet) -> X ())
-> (WindowSet -> WindowSet) -> X ()
forall a b. (a -> b) -> a -> b
$ 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
ws
             X ()
x
             (WindowSet -> WindowSet) -> X ()
windows ((WindowSet -> WindowSet) -> X ())
-> (WindowSet -> WindowSet) -> X ()
forall a b. (a -> b) -> a -> b
$ Focus -> WindowSet -> WindowSet -> WindowSet
setFocus Focus
foc WindowSet
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 :: ScreenId -> WorkspaceId -> WindowSet -> WindowSet
viewOnScreen ScreenId
sid WorkspaceId
i =
    (WindowSet -> WindowSet)
-> Focus -> ScreenId -> WindowSet -> WindowSet
onScreen (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
i) (WorkspaceId -> Focus
FocusTag WorkspaceId
i) ScreenId
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 :: ScreenId -> WorkspaceId -> WindowSet -> WindowSet
greedyViewOnScreen ScreenId
sid WorkspaceId
i =
    (WindowSet -> WindowSet)
-> Focus -> ScreenId -> WindowSet -> WindowSet
onScreen (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
greedyView WorkspaceId
i) (WorkspaceId -> Focus
FocusTagVisible WorkspaceId
i) ScreenId
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 :: ScreenId -> WorkspaceId -> WindowSet -> WindowSet
onlyOnScreen ScreenId
sid WorkspaceId
i =
    (WindowSet -> WindowSet)
-> Focus -> ScreenId -> WindowSet -> WindowSet
onScreen (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
i) Focus
FocusCurrent ScreenId
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 :: ScreenId -> WorkspaceId -> WindowSet -> WindowSet
toggleOnScreen ScreenId
sid WorkspaceId
i =
    (WindowSet -> WindowSet)
-> Focus -> ScreenId -> WindowSet -> WindowSet
onScreen ((WorkspaceId -> WindowSet -> WindowSet)
-> WorkspaceId -> WindowSet -> WindowSet
toggleOrView' 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
i) Focus
FocusCurrent ScreenId
sid

-- | @toggleOrView@ from "XMonad.Actions.CycleWS" for @onScreen@ with greedyView
toggleGreedyOnScreen :: ScreenId    -- ^ screen id
                     -> WorkspaceId -- ^ index of the workspace
                     -> WindowSet   -- ^ current stack
                     -> WindowSet
toggleGreedyOnScreen :: ScreenId -> WorkspaceId -> WindowSet -> WindowSet
toggleGreedyOnScreen ScreenId
sid WorkspaceId
i =
    (WindowSet -> WindowSet)
-> Focus -> ScreenId -> WindowSet -> WindowSet
onScreen ((WorkspaceId -> WindowSet -> WindowSet)
-> WorkspaceId -> WindowSet -> WindowSet
toggleOrView' 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
greedyView WorkspaceId
i) Focus
FocusCurrent ScreenId
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' :: (WorkspaceId -> WindowSet -> WindowSet)
-> WorkspaceId -> WindowSet -> WindowSet
toggleOrView' WorkspaceId -> WindowSet -> WindowSet
f WorkspaceId
i WindowSet
st = WindowSet -> Maybe WindowSet -> WindowSet
forall a. a -> Maybe a -> a
fromMaybe (WorkspaceId -> WindowSet -> WindowSet
f WorkspaceId
i WindowSet
st) (Maybe WindowSet -> WindowSet) -> Maybe WindowSet -> WindowSet
forall a b. (a -> b) -> a -> b
$ do
    let st' :: [Workspace WorkspaceId (Layout Window) Window]
st' = WindowSet -> [Workspace WorkspaceId (Layout Window) Window]
forall i l a sid sd. StackSet i l a sid sd -> [Workspace i l a]
hidden WindowSet
st
    -- make sure we actually have to do something
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ WorkspaceId
i WorkspaceId -> WorkspaceId -> Bool
forall a. Eq a => a -> a -> Bool
== (Workspace WorkspaceId (Layout Window) Window -> WorkspaceId
forall i l a. Workspace i l a -> i
tag (Workspace WorkspaceId (Layout Window) Window -> WorkspaceId)
-> (Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
    -> Workspace WorkspaceId (Layout Window) Window)
-> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
-> WorkspaceId
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
-> Workspace WorkspaceId (Layout Window) Window
forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
workspace (Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
 -> WorkspaceId)
-> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
-> WorkspaceId
forall a b. (a -> b) -> a -> b
$ WindowSet
-> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
current WindowSet
st)
    case [Workspace WorkspaceId (Layout Window) Window]
st' of
      []      -> Maybe WindowSet
forall a. Maybe a
forall (f :: * -> *) a. Alternative f => f a
empty
      (Workspace WorkspaceId (Layout Window) Window
h : [Workspace WorkspaceId (Layout Window) Window]
_) -> WindowSet -> Maybe WindowSet
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (WindowSet -> Maybe WindowSet) -> WindowSet -> Maybe WindowSet
forall a b. (a -> b) -> a -> b
$ WorkspaceId -> WindowSet -> WindowSet
f (Workspace WorkspaceId (Layout Window) Window -> WorkspaceId
forall i l a. Workspace i l a -> i
tag Workspace WorkspaceId (Layout Window) Window
h) WindowSet
st  -- finally, toggle!

-- $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.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
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.