aboutsummaryrefslogtreecommitdiffstats
path: root/XMonad/Hooks/DynamicHooks.hs
blob: f4751d97f459368046f9f657eda544e1dd970f49 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Hooks.DynamicHooks
-- 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
  initDynamicHooks
  ,dynamicMasterHook
  ,addDynamicHook
  ,updateDynamicHook
  ,oneShotHook
  ) where

import XMonad
import System.IO

import Data.List
import Data.Maybe (listToMaybe)
import Data.Monoid
import Data.IORef

-- $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@.
--
-- First, you must execute 'initDynamicHooks' from 'main' in your @xmonad.hs@:
--
-- > dynHooksRef <- initDynamicHooks
--
-- and then pass this value to the other functions in this module.
--
-- You also need to add the base 'ManageHook':
--
-- > xmonad { manageHook = myManageHook <+> dynamicMasterHook dynHooksRef }
--
-- You must include this @dynHooksRef@ value when using the functions in this
-- module:
--
-- > xmonad { keys = myKeys `Data.Map.union` Data.Map.fromList
-- >                   [((modm, xK_i), oneShotHook dynHooksRef
-- >                    "FFlaunchHook" (className =? "firefox") (doShift "3")
-- >                    >> spawn "firefox")
-- >                   ,((modm, xK_u), addDynamicHook dynHooksRef
-- >                     (className =? "example" --> doFloat))
-- >                   ,((modm, xK_y), updatePermanentHook dynHooksRef
-- >                     (const idHook))) ]  -- resets the permanent hook.
--

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


-- | Creates the 'IORef' that stores the dynamically created 'ManageHook's.
initDynamicHooks :: IO (IORef DynamicHooks)
initDynamicHooks = newIORef (DynamicHooks { transients = [],
                                            permanent = idHook })


-- this hook is always executed, and the IORef's contents 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 :: IORef DynamicHooks -> ManageHook
dynamicMasterHook ref = return True -->
                  (ask >>= \w -> liftX (do
  dh <- io $ readIORef ref
  (Endo f)  <- runQuery (permanent dh) w
  ts <- mapM (\(q,a) -> runQuery q w >>= \x -> return (x,(q, a))) (transients dh)
  let (ts',nts) = partition fst ts
  gs <- mapM (flip runQuery w . snd . snd) ts'
  let (Endo g) = maybe (Endo id) id $ listToMaybe gs
  io $ writeIORef ref $ dh { transients = map snd nts }
  return $ Endo $ f . g
                                       ))

-- | Appends the given 'ManageHook' to the permanent dynamic 'ManageHook'.
addDynamicHook :: IORef DynamicHooks -> ManageHook -> X ()
addDynamicHook ref m = updateDynamicHook ref (<+> m)


-- | Modifies the permanent 'ManageHook' with an arbitrary function.
updateDynamicHook :: IORef DynamicHooks -> (ManageHook -> ManageHook) -> X ()
updateDynamicHook ref f =
    io $ modifyIORef ref $ \dh -> 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 :: IORef DynamicHooks -> Query Bool -> ManageHook -> X ()
oneShotHook ref q a =
  io $ modifyIORef ref
         $ \dh -> dh { transients = (q,a):(transients dh) }