content: {-# 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
  