content: {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  -----------------------------------------------------------------------------
  -- |
  -- Module       : XMonad.Actions.PhysicalScreens
  -- Copyright    : (c) Nelson Elhage <nelhage@mit.edu>
  -- License      : BSD
  --
  -- Maintainer   : Nelson Elhage <nelhage@mit.edu>
  -- Stability    : unstable
  -- Portability  : unportable
  --
  -- Manipulate screens ordered by physical location instead of ID
  -----------------------------------------------------------------------------
  
  module XMonad.Actions.PhysicalScreens (
                                          -- * Usage
                                          -- $usage
                                          PhysicalScreen(..)
                                        , getScreen
                                        , viewScreen
                                        , sendToScreen
                                        , onNextNeighbour
                                        , onPrevNeighbour
                                        ) where
  
  import XMonad
  import qualified XMonad.StackSet as W
  
  import Data.List (sortBy,findIndex)
  import Data.Function (on)
  
  {- $usage
  
  This module allows you name Xinerama screens from XMonad using their
  physical location relative to each other (as reported by Xinerama),
  rather than their @ScreenID@ s, which are arbitrarily determined by
  your X server and graphics hardware.
  
  Screens are ordered by the upper-left-most corner, from top-to-bottom
  and then left-to-right.
  
  Example usage in your @~\/.xmonad\/xmonad.hs@ file:
  
  > import XMonad.Actions.PhysicalScreens
  
  > , ((modMask, xK_a), onPrevNeighbour W.view)
  > , ((modMask, xK_o), onNextNeighbour W.view)
  > , ((modMask .|. shiftMask, xK_a), onPrevNeighbour W.shift)
  > , ((modMask .|. shiftMask, xK_o), onNextNeighbour W.shift)
  
  > --
  > -- mod-{w,e,r}, Switch to physical/Xinerama screens 1, 2, or 3
  > -- mod-shift-{w,e,r}, Move client to screen 1, 2, or 3
  > --
  > [((modm .|. mask, key), f sc)
  >     | (key, sc) <- zip [xK_w, xK_e, xK_r] [0..]
  >     , (f, mask) <- [(viewScreen, 0), (sendToScreen, shiftMask)]]
  
  For detailed instructions on editing your key bindings, see
  "XMonad.Doc.Extending#Editing_key_bindings".
   -}
  
  -- | The type of the index of a screen by location
  newtype PhysicalScreen = P Int deriving (Eq,Ord,Show,Read,Enum,Num,Integral,Real)
  
  -- | Translate a physical screen index to a "ScreenId"
  getScreen :: PhysicalScreen -> X (Maybe ScreenId)
  getScreen (P i) = do w <- gets windowset
                       let screens = W.current w : W.visible w
                       if i<0 || i >= length screens
                        then return Nothing
                        else let ss = sortBy (cmpScreen `on` (screenRect . W.screenDetail)) screens
                             in return $ Just $ W.screen $ ss !! i
  
  -- | Switch to a given physical screen
  viewScreen :: PhysicalScreen -> X ()
  viewScreen p = do i <- getScreen p
                    whenJust i $ \s -> do
                    w <- screenWorkspace s
                    whenJust w $ windows . W.view
  
  -- | Send the active window to a given physical screen
  sendToScreen :: PhysicalScreen -> X ()
  sendToScreen p = do i <- getScreen p
                      whenJust i $ \s -> do
                        w <- screenWorkspace s
                        whenJust w $ windows . W.shift
  
  -- | Compare two screens by their top-left corners, ordering
  -- | top-to-bottom and then left-to-right.
  cmpScreen :: Rectangle -> Rectangle -> Ordering
  cmpScreen (Rectangle x1 y1 _ _) (Rectangle x2 y2 _ _) = compare (y1,x1) (y2,x2)
  
  
  -- | Get ScreenId for neighbours of the current screen based on position offset.
  getNeighbour :: Int -> X ScreenId
  getNeighbour d = do w <- gets windowset
                      let ss = map W.screen $ sortBy (cmpScreen `on` (screenRect . W.screenDetail)) $ W.current w : W.visible w
                          curPos = maybe 0 id $ findIndex (== W.screen (W.current w)) ss
                          pos = (curPos + d) `mod` length ss
                      return $ ss !! pos
  
  neighbourWindows :: Int -> (WorkspaceId -> WindowSet -> WindowSet) -> X ()
  neighbourWindows d f = do s <- getNeighbour d
                            w <- screenWorkspace s
                            whenJust w $ windows . f
  
  -- | Apply operation on a WindowSet with the WorkspaceId of the next screen in the physical order as parameter.
  onNextNeighbour :: (WorkspaceId -> WindowSet -> WindowSet) -> X ()
  onNextNeighbour = neighbourWindows 1
  
  -- | Apply operation on a WindowSet with the WorkspaceId of the previous screen in the physical order as parameter.
  onPrevNeighbour :: (WorkspaceId -> WindowSet -> WindowSet) -> X ()
  onPrevNeighbour = neighbourWindows (-1)
  