    {-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Hooks.FadeWindows
    -- Copyright   :  Brandon S Allbery KF8NH <allbery.b@gmail.com>
    -- License     :  BSD
    --
    -- Maintainer  :  Brandon S Allbery KF8NH
    -- Stability   :  unstable
    -- Portability :  unportable
    --
    -- A more flexible and general compositing interface than FadeInactive.
    -- Windows can be selected and opacity specified by means of FadeHooks,
    -- which are very similar to ManageHooks and use the same machinery.
    --
    -----------------------------------------------------------------------------

    module XMonad.Hooks.FadeWindows (-- * Usage
                                     -- $usage

                                     -- * The 'logHook' for window fading
                                     fadeWindowsLogHook

                                     -- * The 'FadeHook'
                                    ,FadeHook
                                    ,Opacity
                                    ,idFadeHook

                                     -- * Predefined 'FadeHook's
                                    ,opaque
                                    ,solid
                                    ,transparent
                                    ,invisible
                                    ,transparency
                                    ,translucence
                                    ,fadeBy
                                    ,opacity
                                    ,fadeTo

                                    -- * 'handleEventHook' for mapped/unmapped windows
                                    ,fadeWindowsEventHook

                                    -- * 'doF' for simple hooks
                                    ,doS

                                    -- * Useful 'Query's for 'FadeHook's
                                    ,isFloating
                                    ,isUnfocused
                                    ) where

    import           XMonad.Core
    import           XMonad.ManageHook                       (liftX)
    import qualified XMonad.StackSet             as W

    import           XMonad.Hooks.FadeInactive               (setOpacity
                                                             ,isUnfocused
                                                             )

    import           Control.Monad                           (forM_)
    import           Control.Monad.Reader                    (ask
                                                             ,asks)
    import           Control.Monad.State                     (gets)
    import qualified Data.Map                    as M
    import           Data.Monoid

    import           Graphics.X11.Xlib.Extras                (Event(..))

    -- $usage
    -- To use this module, make sure your @xmonad@ core supports generalized
    -- 'ManageHook's (check the type of 'idHook'; if it's @ManageHook@ then
    -- your @xmonad@ is too old) and then add @fadeWindowsLogHook@ to your
    -- 'logHook' and @fadeWindowsEventHook@ to your 'handleEventHook':
    --
    -- >     , logHook = fadeWindowsLogHook myFadeHook
    -- >     , handleEventHook = fadeWindowsEventHook
    -- >     {- ... -}
    -- >
    -- > myFadeHook = composeAll [isUnfocused --> transparency 0.2
    -- >                         ,                opaque
    -- >                         ]
    --
    -- The above is like FadeInactive with a fade value of 0.2.
    --
    -- FadeHooks do not accumulate; instead, they compose from right to
    -- left like 'ManageHook's, so the above example @myFadeHook@ will
    -- render unfocused windows at 4/5 opacity and the focused window
    -- as opaque.  The 'opaque' hook above is optional, by the way, as any
    -- unmatched window will be opaque by default.
    --
    -- This module is best used with "XMonad.Hooks.MoreManageHelpers", which
    -- exports a number of Queries that can be used in either @ManageHook@
    -- or @FadeHook@.
    --
    -- Note that you need a compositing manager such as @xcompmgr@,
    -- @dcompmgr@, or @cairo-compmgr@ for window fading to work.  If you
    -- aren't running a compositing manager, the opacity will be recorded
    -- but won't take effect until a compositing manager is started.
    --
    -- For more detailed instructions on editing the 'logHook' see:
    --
    -- "XMonad.Doc.Extending#The_log_hook_and_external_status_bars"
    --
    -- For more detailed instructions on editing the 'handleEventHook',
    -- see:
    --
    -- "XMonad.Doc.Extending#Editing_the_event_hook"
    -- (which sadly doesnt exist at the time of writing...)
    --
    -- /WARNING:/  This module is very good at triggering bugs in
    -- compositing managers.  Symptoms range from windows not being
    -- repainted until the compositing manager is restarted or the
    -- window is unmapped and remapped, to the machine becoming sluggish
    -- until the compositing manager is restarted (at which point a
    -- popup/dialog will suddenly appear; apparently it's getting into
    -- a tight loop trying to fade the popup in).  I find it useful to
    -- have a key binding to restart the compositing manager; for example,
    --
    -- main = xmonad $ def {
    --                   {- ... -}
    --                 }
    --                 `additionalKeysP`
    --                 [("M-S-4",spawn "killall xcompmgr; sleep 1; xcompmgr -cCfF &")]
    --                 {- ... -}
    --                 ]
    --
    -- (See "XMonad.Util.EZConfig" for 'additionalKeysP'.)

    -- a window opacity to be carried in a Query.  OEmpty is sort of a hack
    -- to make it obay the monoid laws
    data Opacity = Opacity Rational | OEmpty

    instance Monoid Opacity where
      mempty                  = OEmpty
      r      `mappend` OEmpty = r
      _      `mappend` r      = r

    -- | A FadeHook is similar to a ManageHook, but records window opacity.
    type FadeHook = Query Opacity

    -- | Render a window fully opaque.
    opaque :: FadeHook
    opaque =  doS (Opacity 1)

    -- | Render a window fully transparent.
    transparent :: FadeHook
    transparent =  doS (Opacity 0)

    -- | Specify a window's transparency.
    transparency :: Rational -- ^ The window's transparency as a fraction.
                             --   @transparency 1@ is the same as 'transparent',
                             --   whereas @transparency 0@ is the same as 'opaque'.
                 -> FadeHook
    transparency =  doS . Opacity . (1-) . clampRatio

    -- | Specify a window's opacity; this is the inverse of 'transparency'.
    opacity :: Rational -- ^ The opacity of a window as a fraction.
                        --   @opacity 1@ is the same as 'opaque',
                        --   whereas @opacity 0@ is the same as 'transparent'.
            -> FadeHook
    opacity =  doS . Opacity . clampRatio

    fadeTo, translucence, fadeBy :: Rational -> FadeHook
    -- ^ An alias for 'transparency'.
    fadeTo       = transparency
    -- ^ An alias for 'transparency'.
    translucence = transparency
    -- ^ An alias for 'transparency'.
    fadeBy       = opacity

    invisible, solid :: FadeHook
    -- ^ An alias for 'transparent'.
    invisible    = transparent
    -- ^ An alias for 'opaque'.
    solid        = opaque

    -- | Like 'doF', but usable with 'ManageHook'-like hooks that
    -- aren't 'Query' wrapped around transforming functions ('Endo').
    doS :: Monoid m => m -> Query m
    doS =  return

    -- | The identity 'FadeHook', which renders windows 'opaque'.
    idFadeHook :: FadeHook
    idFadeHook =  opaque

    -- | A Query to determine if a window is floating.
    isFloating :: Query Bool
    isFloating =  ask >>= \w -> liftX . gets $ M.member w . W.floating . windowset

    -- boring windows can't be seen outside of a layout, so we watch messages with
    -- a dummy LayoutModifier and stow them in a persistent bucket.  this is not
    -- entirely reliable given that boringAuto still isn't observable; we just hope
    -- those aren't visible and won;t be affected anyway
    -- @@@ punted for now, will be a separate module.  it's still slimy, though

    -- | A 'logHook' to fade windows under control of a 'FadeHook', which is
    --   similar to but not identical to 'ManageHook'.
    fadeWindowsLogHook   :: FadeHook -> X ()
    fadeWindowsLogHook h =  withWindowSet $ \s -> do
      let visibleWins = (W.integrate' . W.stack . W.workspace . W.current $ s) ++
                        concatMap (W.integrate' . W.stack . W.workspace) (W.visible s)
      forM_ visibleWins $ \w -> do
        o <- userCodeDef (Opacity 1) (runQuery h w)
        setOpacity w $ case o of
                         OEmpty    -> 0.93
                         Opacity r -> r

    -- | A 'handleEventHook' to handle fading and unfading of newly mapped
    --   or unmapped windows; this avoids problems with layouts such as
    --   "XMonad.Layout.Full" or "XMonad.Layout.Tabbed".  This hook may
    --   also be useful with "XMonad.Hooks.FadeInactive".
    fadeWindowsEventHook                     :: Event -> X All
    fadeWindowsEventHook (MapNotifyEvent {}) =
      -- we need to run the fadeWindowsLogHook.  only one way...
      asks config >>= logHook >> return (All True)
    fadeWindowsEventHook _                   =  return (All True)

    -- A utility to clamp opacity fractions to the range (0,1)
    clampRatio   :: Rational         -> Rational
    clampRatio r |  r >= 0 && r <= 1 =  r
                 |  r < 0            =  0
                 |  otherwise        =  1