    {-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances #-}
    {-# LANGUAGE PatternGuards #-}
    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Layout.Stoppable
    -- Copyright   :  (c) Anton Vorontsov <anton@enomsg.org> 2014
    -- License     :  BSD-style (as xmonad)
    --
    -- Maintainer  :  Anton Vorontsov <anton@enomsg.org>
    -- Stability   :  unstable
    -- Portability :  unportable
    --
    -- This module implements a special kind of layout modifier, which when
    -- applied to a layout, causes xmonad to stop all non-visible processes.
    -- In a way, this is a sledge-hammer for applications that drain power.
    -- For example, given a web browser on a stoppable workspace, once the
    -- workspace is hidden the web browser will be stopped.
    --
    -- Note that the stopped application won't be able to communicate with X11
    -- clipboard. For this, the module actually stops applications after a
    -- certain delay, giving a chance for a user to complete copy-paste
    -- sequence. By default, the delay equals to 15 seconds, it is
    -- configurable via 'Stoppable' constructor.
    --
    -- The stoppable modifier prepends a mark (by default equals to
    -- \"Stoppable\") to the layout description (alternatively, you can choose
    -- your own mark and use it with 'Stoppable' constructor). The stoppable
    -- layout (identified by a mark) spans to multiple workspaces, letting you
    -- to create groups of stoppable workspaces that only stop processes when
    -- none of the workspaces are visible, and conversely, unfreezing all
    -- processes even if one of the stoppable workspaces are visible.
    --
    -- To stop the process we use signals, which works for most cases. For
    -- processes that tinker with signal handling (debuggers), another
    -- (Linux-centric) approach may be used. See
    -- <https://www.kernel.org/doc/Documentation/cgroups/freezer-subsystem.txt>
    --
    -----------------------------------------------------------------------------

    module XMonad.Layout.Stoppable
        ( -- $usage
          Stoppable(..)
        , stoppable
        ) where

    import XMonad
    import XMonad.Actions.WithAll
    import XMonad.Util.WindowProperties
    import XMonad.Util.RemoteWindows
    import XMonad.Util.Timer
    import XMonad.StackSet hiding (filter)
    import XMonad.Layout.LayoutModifier
    import System.Posix.Signals
    import Data.Maybe
    import Control.Monad

    -- $usage
    -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
    --
    -- > import XMonad
    -- > import XMonad.Layout.Stoppable
    -- >
    -- > main = xmonad def
    -- >    { layoutHook = layoutHook def ||| stoppable (layoutHook def) }
    --
    -- Note that the module has to distinguish between local and remote
    -- proccesses, which means that it needs to know the hostname, so it looks
    -- for environment variables (e.g. HOST).
    --
    -- Environment variables will work for most cases, but won't work if the
    -- hostname changes. To cover dynamic hostnames case, in addition to
    -- layoutHook you have to provide manageHook from
    -- "XMonad.Util.RemoteWindows" module.
    --
    -- For more detailed instructions on editing the layoutHook see:
    --
    -- "XMonad.Doc.Extending#Editing_the_layout_hook"

    signalWindow :: Signal -> Window -> X ()
    signalWindow s w = do
        pid <- getProp32s "_NET_WM_PID" w
        io $ (signalProcess s . fromIntegral) `mapM_` fromMaybe [] pid

    signalLocalWindow :: Signal -> Window -> X ()
    signalLocalWindow s w  = isLocalWindow w >>= flip when (signalWindow s w)

    withAllOn :: (a -> X ()) -> Workspace i l a -> X ()
    withAllOn f wspc = f `mapM_` integrate' (stack wspc)

    withAllFiltered :: (Workspace i l a -> Bool)
                    -> [Workspace i l a]
                    -> (a -> X ()) -> X ()
    withAllFiltered p wspcs f = withAllOn f `mapM_` filter p wspcs

    sigStoppableWorkspacesHook :: String -> X ()
    sigStoppableWorkspacesHook k = do
        ws <- gets windowset
        withAllFiltered isStoppable (hidden ws) (signalLocalWindow sigSTOP)
      where
        isStoppable ws = k `elem` words (description $ layout ws)

    -- | Data type for ModifiedLayout. The constructor lets you to specify a
    -- custom mark/description modifier and a delay. You can also use
    -- 'stoppable' helper function.
    data Stoppable a = Stoppable
        { mark :: String
        , delay :: Rational
        , timer :: Maybe TimerId
        } deriving (Show,Read)

    instance LayoutModifier Stoppable Window where
        modifierDescription = mark

        hook _   = withAll $ signalLocalWindow sigCONT

        handleMess (Stoppable m _ (Just tid)) msg
            | Just ev <- fromMessage msg = handleTimer tid ev run
              where run = sigStoppableWorkspacesHook m >> return Nothing
        handleMess (Stoppable m d _) msg
            | Just Hide <- fromMessage msg =
                (Just . Stoppable m d . Just) `liftM` startTimer d
            | otherwise = return Nothing

    -- | Convert a layout to a stoppable layout using the default mark
    -- (\"Stoppable\") and a delay of 15 seconds.
    stoppable :: l a -> ModifiedLayout Stoppable l a
    stoppable = ModifiedLayout (Stoppable "Stoppable" 15 Nothing)