-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Hooks.ToggleHook
-- Description :  Hook and keybindings for toggling hook behavior.
-- Copyright   :  Ben Boeckel <mathstuf@gmail.com>
-- License     :  BSD-style (see LICENSE)
--
-- Maintainer  :  Ben Boeckel <mathstuf@gmail.com>
-- Stability   :  unstable
-- Portability :  unportable
--
-- Hook and keybindings for toggling hook behavior.
-----------------------------------------------------------------------------

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

                                 -- * The hook
                                 toggleHook
                               , toggleHook'

                                 -- * Actions
                               , hookNext
                               , toggleHookNext
                               , hookAllNew
                               , toggleHookAllNew

                                 -- * Queries
                               , willHook
                               , willHookNext
                               , willHookAllNew

                                 -- * Status bar utilities
                                 -- $pp
                               , willHookNextPP
                               , willHookAllNewPP
                               , runLogHook ) where

import Prelude hiding (all)

import XMonad
import XMonad.Prelude (guard, join)
import qualified XMonad.Util.ExtensibleState as XS

import Control.Arrow (first, second)

import Data.Map

{- Helper functions -}

_set :: String -> ((a -> a) -> (Bool, Bool) -> (Bool, Bool)) -> a -> X ()
_set :: forall a.
String -> ((a -> a) -> (Bool, Bool) -> (Bool, Bool)) -> a -> X ()
_set String
n (a -> a) -> (Bool, Bool) -> (Bool, Bool)
f a
b = String -> ((Bool, Bool) -> (Bool, Bool)) -> X ()
modify' String
n ((a -> a) -> (Bool, Bool) -> (Bool, Bool)
f forall a b. (a -> b) -> a -> b
$ forall a b. a -> b -> a
const a
b)

_toggle :: String -> ((Bool -> Bool) -> (Bool, Bool) -> (Bool, Bool)) -> X ()
_toggle :: String -> ((Bool -> Bool) -> (Bool, Bool) -> (Bool, Bool)) -> X ()
_toggle String
n (Bool -> Bool) -> (Bool, Bool) -> (Bool, Bool)
f = String -> ((Bool, Bool) -> (Bool, Bool)) -> X ()
modify' String
n ((Bool -> Bool) -> (Bool, Bool) -> (Bool, Bool)
f Bool -> Bool
not)

_get :: String -> ((Bool, Bool) -> a) -> X a
_get :: forall a. String -> ((Bool, Bool) -> a) -> X a
_get String
n (Bool, Bool) -> a
f = forall a (m :: * -> *) b.
(ExtensionClass a, XLike m) =>
(a -> b) -> m b
XS.gets forall a b. (a -> b) -> a -> b
$ (Bool, Bool) -> a
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall k a. Ord k => a -> k -> Map k a -> a
findWithDefault (Bool
False, Bool
False) String
n forall b c a. (b -> c) -> (a -> b) -> a -> c
. HookState -> Map String (Bool, Bool)
hooks)

_pp :: String -> ((Bool, Bool) -> Bool) -> String -> (String -> String) -> X (Maybe String)
_pp :: String
-> ((Bool, Bool) -> Bool)
-> String
-> (String -> String)
-> X (Maybe String)
_pp String
n (Bool, Bool) -> Bool
f String
s String -> String
st = (\Bool
b -> forall (f :: * -> *). Alternative f => Bool -> f ()
guard Bool
b forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall a. a -> Maybe a
Just (String -> String
st String
s)) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. String -> ((Bool, Bool) -> a) -> X a
_get String
n (Bool, Bool) -> Bool
f

{- The current state is kept here -}

newtype HookState = HookState { HookState -> Map String (Bool, Bool)
hooks :: Map String (Bool, Bool) } deriving (ReadPrec [HookState]
ReadPrec HookState
Int -> ReadS HookState
ReadS [HookState]
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [HookState]
$creadListPrec :: ReadPrec [HookState]
readPrec :: ReadPrec HookState
$creadPrec :: ReadPrec HookState
readList :: ReadS [HookState]
$creadList :: ReadS [HookState]
readsPrec :: Int -> ReadS HookState
$creadsPrec :: Int -> ReadS HookState
Read, Int -> HookState -> String -> String
[HookState] -> String -> String
HookState -> String
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
showList :: [HookState] -> String -> String
$cshowList :: [HookState] -> String -> String
show :: HookState -> String
$cshow :: HookState -> String
showsPrec :: Int -> HookState -> String -> String
$cshowsPrec :: Int -> HookState -> String -> String
Show)

instance ExtensionClass HookState where
    initialValue :: HookState
initialValue = Map String (Bool, Bool) -> HookState
HookState forall k a. Map k a
empty
    extensionType :: HookState -> StateExtension
extensionType = forall a. (Read a, Show a, ExtensionClass a) => a -> StateExtension
PersistentExtension

modify' :: String -> ((Bool, Bool) -> (Bool, Bool)) -> X ()
modify' :: String -> ((Bool, Bool) -> (Bool, Bool)) -> X ()
modify' String
n (Bool, Bool) -> (Bool, Bool)
f = forall a (m :: * -> *).
(ExtensionClass a, XLike m) =>
(a -> a) -> m ()
XS.modify (Map String (Bool, Bool) -> HookState
HookState forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map String (Bool, Bool) -> Map String (Bool, Bool)
setter forall b c a. (b -> c) -> (a -> b) -> a -> c
. HookState -> Map String (Bool, Bool)
hooks)
    where
        setter :: Map String (Bool, Bool) -> Map String (Bool, Bool)
setter Map String (Bool, Bool)
m = forall k a. Ord k => k -> a -> Map k a -> Map k a
insert String
n ((Bool, Bool) -> (Bool, Bool)
f (forall k a. Ord k => a -> k -> Map k a -> a
findWithDefault (Bool
False, Bool
False) String
n Map String (Bool, Bool)
m)) Map String (Bool, Bool)
m

-- $usage
-- This module provides actions (that can be set as keybindings)
-- to be able to cause hooks to be occur on a conditional basis.
--
-- You can use it by including the following in your @xmonad.hs@:
--
-- > import XMonad.Hooks.ToggleHook
--
-- and adding 'toggleHook name hook' to your 'ManageHook' where @name@ is the
-- name of the hook and @hook@ is the hook to execute based on the state.
--
-- > myManageHook = toggleHook "float" doFloat <> manageHook def
--
-- Additionally, toggleHook' is provided to toggle between two hooks (rather
-- than on/off).
--
-- > myManageHook = toggleHook' "oldfocus" (const id) W.focusWindow <> manageHook def
--
-- The 'hookNext' and 'toggleHookNext' functions can be used in key
-- bindings to set whether the hook is applied or not.
--
-- > , ((modm, xK_e), toggleHookNext "float")
--
-- 'hookAllNew' and 'toggleHookAllNew' are similar but float all
-- spawned windows until disabled again.
--
-- > , ((modm, xK_r), toggleHookAllNew "float")

-- | This 'ManageHook' will selectively apply a hook as set
-- by 'hookNext' and 'hookAllNew'.
toggleHook :: String -> ManageHook -> ManageHook
toggleHook :: String -> ManageHook -> ManageHook
toggleHook String
n ManageHook
h = String -> ManageHook -> ManageHook -> ManageHook
toggleHook' String
n ManageHook
h forall m. Monoid m => m
idHook

toggleHook' :: String -> ManageHook -> ManageHook -> ManageHook
toggleHook' :: String -> ManageHook -> ManageHook -> ManageHook
toggleHook' String
n ManageHook
th ManageHook
fh = do Map String (Bool, Bool)
m <- forall a. X a -> Query a
liftX forall a b. (a -> b) -> a -> b
$ forall a (m :: * -> *) b.
(ExtensionClass a, XLike m) =>
(a -> b) -> m b
XS.gets HookState -> Map String (Bool, Bool)
hooks
                         (Bool
next, Bool
all) <- forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => a -> k -> Map k a -> a
findWithDefault (Bool
False, Bool
False) String
n Map String (Bool, Bool)
m
                         forall a. X a -> Query a
liftX forall a b. (a -> b) -> a -> b
$ forall a (m :: * -> *). (ExtensionClass a, XLike m) => a -> m ()
XS.put forall a b. (a -> b) -> a -> b
$ Map String (Bool, Bool) -> HookState
HookState forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> a -> Map k a -> Map k a
insert String
n (Bool
False, Bool
all) Map String (Bool, Bool)
m
                         if Bool
next Bool -> Bool -> Bool
|| Bool
all then ManageHook
th else ManageHook
fh

-- | @hookNext name True@ arranges for the next spawned window to
-- have the hook @name@ applied, @hookNext name False@ cancels it.
hookNext :: String -> Bool -> X ()
hookNext :: String -> Bool -> X ()
hookNext String
n = forall a.
String -> ((a -> a) -> (Bool, Bool) -> (Bool, Bool)) -> a -> X ()
_set String
n forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (b, d) (c, d)
first

toggleHookNext :: String -> X ()
toggleHookNext :: String -> X ()
toggleHookNext String
n = String -> ((Bool -> Bool) -> (Bool, Bool) -> (Bool, Bool)) -> X ()
_toggle String
n forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (b, d) (c, d)
first

-- | @hookAllNew name True@ arranges for new windows to
-- have the hook @name@ applied, @hookAllNew name False@ cancels it
hookAllNew :: String -> Bool -> X ()
hookAllNew :: String -> Bool -> X ()
hookAllNew String
n = forall a.
String -> ((a -> a) -> (Bool, Bool) -> (Bool, Bool)) -> a -> X ()
_set String
n forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (d, b) (d, c)
second

toggleHookAllNew :: String -> X ()
toggleHookAllNew :: String -> X ()
toggleHookAllNew String
n = String -> ((Bool -> Bool) -> (Bool, Bool) -> (Bool, Bool)) -> X ()
_toggle String
n forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (d, b) (d, c)
second

-- | Query what will happen at the next ManageHook call for the hook @name@.
willHook :: String -> X Bool
willHook :: String -> X Bool
willHook String
n = String -> X Bool
willHookNext String
n forall (m :: * -> *). Monad m => m Bool -> m Bool -> m Bool
<||> String -> X Bool
willHookAllNew String
n

-- | Whether the next window will trigger the hook @name@.
willHookNext :: String -> X Bool
willHookNext :: String -> X Bool
willHookNext String
n = forall a. String -> ((Bool, Bool) -> a) -> X a
_get String
n forall a b. (a, b) -> a
fst

-- | Whether new windows will trigger the hook @name@.
willHookAllNew :: String -> X Bool
willHookAllNew :: String -> X Bool
willHookAllNew String
n = forall a. String -> ((Bool, Bool) -> a) -> X a
_get String
n forall a b. (a, b) -> b
snd

-- $pp
-- The following functions are used to display the current
-- state of 'hookNext' and 'hookAllNew' in your
-- "XMonad.Hooks.StatusBar". 'willHookNextPP' and
-- 'willHookAllNewPP' should be added to the
-- 'XMonad.Hooks.StatusBar.PP.ppExtras' field of your
-- "XMonad.Hooks.StatusBar.PP".
--
-- Use 'runLogHook' to refresh the output of your 'logHook', so
-- that the effects of a 'hookNext'/... will be visible
-- immediately:
--
-- > , ((modm, xK_e), toggleHookNext "float" >> runLogHook)
--
-- The @String -> String@ parameters to 'willHookNextPP' and
-- 'willHookAllNewPP' will be applied to their output, you
-- can use them to set the text color, etc., or you can just
-- pass them 'id'.

willHookNextPP :: String -> (String -> String) -> X (Maybe String)
willHookNextPP :: String -> (String -> String) -> X (Maybe String)
willHookNextPP String
n = String
-> ((Bool, Bool) -> Bool)
-> String
-> (String -> String)
-> X (Maybe String)
_pp String
n forall a b. (a, b) -> a
fst String
"Next"

willHookAllNewPP :: String -> (String -> String) -> X (Maybe String)
willHookAllNewPP :: String -> (String -> String) -> X (Maybe String)
willHookAllNewPP String
n = String
-> ((Bool, Bool) -> Bool)
-> String
-> (String -> String)
-> X (Maybe String)
_pp String
n forall a b. (a, b) -> b
snd String
"All"

runLogHook :: X ()
runLogHook :: X ()
runLogHook = forall (m :: * -> *) a. Monad m => m (m a) -> m a
join forall a b. (a -> b) -> a -> b
$ forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks forall a b. (a -> b) -> a -> b
$ forall (l :: * -> *). XConfig l -> X ()
logHook forall b c a. (b -> c) -> (a -> b) -> a -> c
. XConf -> XConfig Layout
config