-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Hooks.DynamicHooks
-- Description :  One-shot and permanent ManageHooks that can be updated at runtime.
-- Copyright   :  (c) Braden Shepherdson 2008
-- License     :  BSD-style (as xmonad)
--
-- Maintainer  :  Braden.Shepherdson@gmail.com
-- Stability   :  unstable
-- Portability :  unportable
--
-- One-shot and permanent ManageHooks that can be updated at runtime.
--
-----------------------------------------------------------------------------

module XMonad.Hooks.DynamicHooks (
  -- * Usage
  -- $usage
  dynamicMasterHook
  ,addDynamicHook
  ,updateDynamicHook
  ,oneShotHook
  ) where

import XMonad
import XMonad.Prelude
import qualified XMonad.Util.ExtensibleState as XS

-- $usage
-- Provides two new kinds of 'ManageHooks' that can be defined at runtime.
--
-- * One-shot 'ManageHooks' that are deleted after they execute.
--
-- * Permanent 'ManageHooks' (unless you want to destroy them)
--
-- Note that you will lose all dynamically defined 'ManageHook's when you @mod+q@!
-- If you want them to last, you should create them as normal in your @xmonad.hs@.
--
-- To use this module, add 'dynamicMasterHook' to your 'manageHook':
--
-- > xmonad { manageHook = myManageHook <> dynamicMasterHook }
--
-- You can then use the supplied functions in your keybindings:
--
-- > ((modMask,xK_a), oneShotHook (className =? "example") doFloat)
--

data DynamicHooks = DynamicHooks
    { DynamicHooks -> [(Query Bool, ManageHook)]
transients :: [(Query Bool, ManageHook)]
    , DynamicHooks -> ManageHook
permanent  :: ManageHook }

instance ExtensionClass DynamicHooks where
    initialValue :: DynamicHooks
initialValue = [(Query Bool, ManageHook)] -> ManageHook -> DynamicHooks
DynamicHooks [] ManageHook
forall m. Monoid m => m
idHook

-- this hook is always executed, and the contents of the stored hooks checked.
-- note that transient hooks are run second, therefore taking precedence
-- over permanent ones on matters such as which workspace to shift to.
-- doFloat and doIgnore are idempotent.
-- | Master 'ManageHook' that must be in your @xmonad.hs@ 'ManageHook'.
dynamicMasterHook :: ManageHook
dynamicMasterHook :: ManageHook
dynamicMasterHook = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window -> (Window -> ManageHook) -> ManageHook
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X (Endo WindowSet) -> ManageHook
forall a. X a -> Query a
liftX (X (Endo WindowSet) -> ManageHook)
-> X (Endo WindowSet) -> ManageHook
forall a b. (a -> b) -> a -> b
$ do
  DynamicHooks
dh <- X DynamicHooks
forall a (m :: * -> *). (ExtensionClass a, XLike m) => m a
XS.get
  (Endo WindowSet -> WindowSet
f)  <- ManageHook -> Window -> X (Endo WindowSet)
forall a. Query a -> Window -> X a
runQuery (DynamicHooks -> ManageHook
permanent DynamicHooks
dh) Window
w
  [(Bool, (Query Bool, ManageHook))]
ts <- ((Query Bool, ManageHook) -> X (Bool, (Query Bool, ManageHook)))
-> [(Query Bool, ManageHook)]
-> X [(Bool, (Query Bool, ManageHook))]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (\(Query Bool
q,ManageHook
a) -> Query Bool -> Window -> X Bool
forall a. Query a -> Window -> X a
runQuery Query Bool
q Window
w X Bool
-> (Bool -> X (Bool, (Query Bool, ManageHook)))
-> X (Bool, (Query Bool, ManageHook))
forall a b. X a -> (a -> X b) -> X b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Bool
x -> (Bool, (Query Bool, ManageHook))
-> X (Bool, (Query Bool, ManageHook))
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool
x,(Query Bool
q, ManageHook
a))) (DynamicHooks -> [(Query Bool, ManageHook)]
transients DynamicHooks
dh)
  let ([(Bool, (Query Bool, ManageHook))]
ts',[(Bool, (Query Bool, ManageHook))]
nts) = ((Bool, (Query Bool, ManageHook)) -> Bool)
-> [(Bool, (Query Bool, ManageHook))]
-> ([(Bool, (Query Bool, ManageHook))],
    [(Bool, (Query Bool, ManageHook))])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition (Bool, (Query Bool, ManageHook)) -> Bool
forall a b. (a, b) -> a
fst [(Bool, (Query Bool, ManageHook))]
ts
  [Endo WindowSet]
gs <- ((Bool, (Query Bool, ManageHook)) -> X (Endo WindowSet))
-> [(Bool, (Query Bool, ManageHook))] -> X [Endo WindowSet]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM ((ManageHook -> Window -> X (Endo WindowSet))
-> Window -> ManageHook -> X (Endo WindowSet)
forall a b c. (a -> b -> c) -> b -> a -> c
flip ManageHook -> Window -> X (Endo WindowSet)
forall a. Query a -> Window -> X a
runQuery Window
w (ManageHook -> X (Endo WindowSet))
-> ((Bool, (Query Bool, ManageHook)) -> ManageHook)
-> (Bool, (Query Bool, ManageHook))
-> X (Endo WindowSet)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Query Bool, ManageHook) -> ManageHook
forall a b. (a, b) -> b
snd ((Query Bool, ManageHook) -> ManageHook)
-> ((Bool, (Query Bool, ManageHook)) -> (Query Bool, ManageHook))
-> (Bool, (Query Bool, ManageHook))
-> ManageHook
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bool, (Query Bool, ManageHook)) -> (Query Bool, ManageHook)
forall a b. (a, b) -> b
snd) [(Bool, (Query Bool, ManageHook))]
ts'
  let (Endo WindowSet -> WindowSet
g) = Endo WindowSet -> Maybe (Endo WindowSet) -> Endo WindowSet
forall a. a -> Maybe a -> a
fromMaybe ((WindowSet -> WindowSet) -> Endo WindowSet
forall a. (a -> a) -> Endo a
Endo WindowSet -> WindowSet
forall a. a -> a
id) (Maybe (Endo WindowSet) -> Endo WindowSet)
-> Maybe (Endo WindowSet) -> Endo WindowSet
forall a b. (a -> b) -> a -> b
$ [Endo WindowSet] -> Maybe (Endo WindowSet)
forall a. [a] -> Maybe a
listToMaybe [Endo WindowSet]
gs
  DynamicHooks -> X ()
forall a (m :: * -> *). (ExtensionClass a, XLike m) => a -> m ()
XS.put (DynamicHooks -> X ()) -> DynamicHooks -> X ()
forall a b. (a -> b) -> a -> b
$ DynamicHooks
dh { transients = map snd nts }
  Endo WindowSet -> X (Endo WindowSet)
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return (Endo WindowSet -> X (Endo WindowSet))
-> Endo WindowSet -> X (Endo WindowSet)
forall a b. (a -> b) -> a -> b
$ (WindowSet -> WindowSet) -> Endo WindowSet
forall a. (a -> a) -> Endo a
Endo ((WindowSet -> WindowSet) -> Endo WindowSet)
-> (WindowSet -> WindowSet) -> Endo WindowSet
forall a b. (a -> b) -> a -> b
$ WindowSet -> WindowSet
f (WindowSet -> WindowSet)
-> (WindowSet -> WindowSet) -> WindowSet -> WindowSet
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WindowSet -> WindowSet
g

-- | Appends the given 'ManageHook' to the permanent dynamic 'ManageHook'.
addDynamicHook :: ManageHook -> X ()
addDynamicHook :: ManageHook -> X ()
addDynamicHook ManageHook
m = (ManageHook -> ManageHook) -> X ()
updateDynamicHook (ManageHook -> ManageHook -> ManageHook
forall a. Semigroup a => a -> a -> a
<> ManageHook
m)

-- | Modifies the permanent 'ManageHook' with an arbitrary function.
updateDynamicHook :: (ManageHook -> ManageHook) -> X ()
updateDynamicHook :: (ManageHook -> ManageHook) -> X ()
updateDynamicHook ManageHook -> ManageHook
f = (DynamicHooks -> DynamicHooks) -> X ()
forall a (m :: * -> *).
(ExtensionClass a, XLike m) =>
(a -> a) -> m ()
XS.modify ((DynamicHooks -> DynamicHooks) -> X ())
-> (DynamicHooks -> DynamicHooks) -> X ()
forall a b. (a -> b) -> a -> b
$ \DynamicHooks
dh -> DynamicHooks
dh { permanent = f (permanent dh) }

-- | Creates a one-shot 'ManageHook'. Note that you have to specify the two
-- parts of the 'ManageHook' separately. Where you would usually write:
--
-- > className =? "example" --> doFloat
--
-- you must call 'oneShotHook' as
--
-- > oneShotHook dynHooksRef (className =? "example) doFloat
--
oneShotHook :: Query Bool -> ManageHook -> X ()
oneShotHook :: Query Bool -> ManageHook -> X ()
oneShotHook Query Bool
q ManageHook
a = (DynamicHooks -> DynamicHooks) -> X ()
forall a (m :: * -> *).
(ExtensionClass a, XLike m) =>
(a -> a) -> m ()
XS.modify ((DynamicHooks -> DynamicHooks) -> X ())
-> (DynamicHooks -> DynamicHooks) -> X ()
forall a b. (a -> b) -> a -> b
$ \DynamicHooks
dh -> DynamicHooks
dh { transients = (q,a):transients dh }