{-# LANGUAGE UnicodeSyntax #-}
{-# OPTIONS_HADDOCK hide #-}
-- | Provides an utility functions for easy and robust workspaces' screen capturing.
module XMonad.Util.WorkspaceScreenshot.Internal
{-# WARNING "Make sure you add `initCapturing' before `xmonad' call in xmonad.hs" #-}
  ( -- * Initialization
    initCapturing
    -- * Screenshoting routines
  , captureWorkspacesWhen
  , captureWorkspacesWhenId
    -- * Defaulting
  , defaultPredicate
  , defaultHook
    -- * Screenshoting layout
  , CapturingLayout(..)
  , horizontally
  , vertically
  ) where

import Control.Applicative ((<$>))
import Control.Concurrent (threadDelay)
import Control.Monad (filterM, foldM_, (>=>))
import Data.Maybe (catMaybes)

import Graphics.UI.Gtk (initGUI, Rectangle(..), drawableGetSize, drawWindowGetDefaultRootWindow)
import Graphics.UI.Gtk.Gdk.Pixbuf (Colorspace(ColorspaceRgb), Pixbuf, pixbufCopyArea, pixbufGetFromDrawable, pixbufGetHeight, pixbufGetWidth, pixbufNew, pixbufSave)
import XMonad hiding (Image, Rectangle)
import qualified XMonad.StackSet as S


-- | Init gtk to enable a possibility of capturing workspaces.
initCapturing  IO ()
initCapturing = initGUI >> return ()
{-# WARNING initCapturing "Make sure you add `initCapturing' before `xmonad' call in xmonad.hs" #-}


-- | Capture screens from workspaces satisfying given predicate.
captureWorkspacesWhen  (WindowSpace  X Bool)  (FilePath  IO ())  CapturingLayout  X ()
captureWorkspacesWhen p hook = captureWorkspacesWhenId (workspaceIdToWindowSpace >=> p) hook
 where
  workspaceIdToWindowSpace i = gets $ head . filter (\w  S.tag w == i) . S.workspaces . windowset


-- | Capture screens from workspaces which id satisfies given predicate.
captureWorkspacesWhenId  (WorkspaceId  X Bool)  (FilePath  IO ())  CapturingLayout  X ()
captureWorkspacesWhenId p hook mode = do
  c  gets $ S.currentTag . windowset
  ps  catMaybes <$> (mapM (\t  windows (S.view t) >> captureScreen) =<< filterM p =<< asks (workspaces . config))
  windows $ S.view c
  xfork $ merge mode ps >>= hook
  return ()


-- | Default predicate. Accepts every available workspace.
defaultPredicate  a  X Bool
defaultPredicate = const (return True)


-- | Default hook. Does nothing.
defaultHook  a  IO ()
defaultHook = const (return ())


-- Capture screen with gtk pixbuf.
-- Delay is necessary to get interfaces rendered.
captureScreen  X (Maybe Pixbuf)
captureScreen = liftIO $
  do threadDelay 100000
     rw  drawWindowGetDefaultRootWindow
     (w, h)  drawableGetSize rw
     pixbufGetFromDrawable rw (Rectangle 0 0 w h)


-- | Layout for resulting capture.
data CapturingLayout = CapturingLayout
  { dimensions  [Pixbuf]  IO (Int, Int) -- ^ Maximum height and maximum width for capture
  , fill  [Pixbuf]  Pixbuf  IO () -- ^ Filling algorithm
  }


-- | Capture screens layout horizontally.
horizontally  CapturingLayout
horizontally = CapturingLayout
  { dimensions = \xs 
      do h  maximum <$> mapM pixbufGetHeight xs
         w  sum <$> mapM pixbufGetWidth xs
         return (h, w)
  , fill = \ps p  foldM_ (addTo p) 0 ps
  }
 where
  addTo  Pixbuf  Int  Pixbuf  IO Int
  addTo p a p' =
    do w'  pixbufGetWidth p'
       h'  pixbufGetHeight p'
       pixbufCopyArea p' 0 0 w' h' p a 0
       return (a + w')


-- | Capture screens layout vertically.
vertically  CapturingLayout
vertically = CapturingLayout
  { dimensions = \xs 
     do h  sum <$> mapM pixbufGetHeight xs
        w  maximum <$> mapM pixbufGetWidth xs
        return (h, w)
  , fill = \ps p  foldM_ (addTo p) 0 ps
  }
 where
  addTo  Pixbuf  Int  Pixbuf  IO Int
  addTo p a p' =
    do w'  pixbufGetWidth p'
       h'  pixbufGetHeight p'
       pixbufCopyArea p' 0 0 w' h' p 0 a
       return (a + h')


-- Contruct final image from the list of pixbufs.
merge  CapturingLayout  [Pixbuf]  IO FilePath
merge layout ps = do
  (h, w)  dimensions layout ps
  p  pixbufNew ColorspaceRgb False 8 w h
  fill layout ps p
  dir  getXMonadDir
  let filepath = (dir ++ "/screenshot.png")
  pixbufSave p filepath "png" []
  return filepath