aboutsummaryrefslogtreecommitdiffstats
path: root/XMonad/Util/Loggers.hs
blob: 36f91d91a4dcea37acd097d85d14637a90cafc49 (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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Util.Loggers
-- Copyright   :  (c) Brent Yorgey, Wirt Wolff
-- License     :  BSD-style (see LICENSE)
--
-- Maintainer  :  <byorgey@gmail.com>
-- Stability   :  unstable
-- Portability :  unportable
--
-- A collection of simple logger functions and formatting utilities
-- which can be used in the 'XMonad.Hooks.DynamicLog.ppExtras' field of
-- a pretty-printing status logger format. See "XMonad.Hooks.DynamicLog"
-- for more information.
-----------------------------------------------------------------------------

module XMonad.Util.Loggers (
    -- * Usage
    -- $usage

      Logger

    -- * System Loggers
    -- $system
    , aumixVolume
    , battery
    , date
    , loadAvg
    , maildirNew, maildirUnread
    , logCmd , logFileCount

    -- * XMonad Loggers
    -- $xmonad
    , logCurrent, logLayout, logTitle

    -- * Formatting Utilities
    -- $format
    , onLogger
    , wrapL, fixedWidthL
    , logSp, padL
    , shortenL
    , dzenColorL, xmobarColorL

    , (<$>)

  ) where

import XMonad (liftIO)
import XMonad.Core
import qualified XMonad.StackSet as W
import XMonad.Hooks.DynamicLog
import XMonad.Util.Font (Align (..))
import XMonad.Util.NamedWindows (getName)

import Control.Applicative ((<$>))
import Control.Exception as E
import Data.List (isPrefixOf, isSuffixOf)
import Data.Maybe (fromMaybe)
import Data.Traversable (traverse)
import System.Directory (getDirectoryContents)
import System.IO
import System.Locale
import System.Process (runInteractiveCommand)
import System.Time

econst :: Monad m => a -> IOException -> m a
econst = const . return

-- $usage
-- Use this module by importing it into your @~\/.xmonad\/xmonad.hs@:
--
-- > import XMonad.Util.Loggers
--
-- Then, add one or more loggers to the
-- 'XMonad.Hooks.DynamicLog.ppExtras' field of your
-- 'XMonad.Hooks.DynamicLoc.PP', possibly with extra formatting .
-- For example:
--
-- >   -- display load averages and a pithy quote along with xmonad status.
-- >   , logHook = dynamicLogWithPP $ defaultPP {
-- >                  ppExtras = [ padL loadAvg, logCmd "fortune -n 40 -s" ]
-- >                }
-- >   -- gives something like " 3.27 3.52 3.26 Drive defensively.  Buy a tank."
--
-- See the formatting section below for another example using
-- a @where@ block to define some formatted loggers for a top-level
-- @myLogHook@.
--
-- Loggers are named either for their function, as in 'battery',
-- 'aumixVolume', and 'maildirNew', or are prefixed with \"log\" when
-- making use of other functions or by analogy with the pp* functions.
-- For example, the logger version of 'XMonad.Hooks.DynamicLog.ppTitle'
-- is 'logTitle', and 'logFileCount' loggerizes the result of file
-- counting code.
--
-- Formatting utility names are generally as short as possible and
-- carry the suffix \"L\". For example, the logger version of
-- 'XMonad.Hooks.DynamicLog.shorten' is 'shortenL'.
--
-- Of course, there is nothing really special about these so-called
-- \"loggers\": they are just @X (Maybe String)@ actions.  So you can
-- use them anywhere you would use an @X (Maybe String)@, not just
-- with DynamicLog.
--
-- Additional loggers welcome!



-- | 'Logger' is just a convenient synonym for @X (Maybe String)@.
type Logger = X (Maybe String)

-- $system

-- | Get the current volume with @aumix@. <http://jpj.net/~trevor/aumix.html>
aumixVolume :: Logger
aumixVolume = logCmd "aumix -vq"

-- | Get the battery status (percent charge and charging\/discharging
--   status). This is an ugly hack and may not work for some people.
--   At some point it would be nice to make this more general\/have
--   fewer dependencies (assumes @\/usr\/bin\/acpi@ and @sed@ are installed.)
battery :: Logger
battery = logCmd "/usr/bin/acpi | sed -r 's/.*?: (.*%).*/\\1/; s/[dD]ischarging, ([0-9]+%)/\\1-/; s/[cC]harging, ([0-9]+%)/\\1+/; s/[cC]harged, //'"

-- | Get the current date and time, and format them via the
--   given format string.  The format used is the same as that used
--   by the C library function strftime; for example,
--   @date \"%a %b %d\"@ might display something like @Tue Feb 19@.
--   For more information see something like
--   <http://www.cplusplus.com/reference/clibrary/ctime/strftime.html>.
date :: String -> Logger
date fmt = io $ do cal <- (getClockTime >>= toCalendarTime)
                   return . Just $ formatCalendarTime defaultTimeLocale fmt cal

-- | Get the load average.  This assumes that you have a
--   utility called @\/usr\/bin\/uptime@ and that you have @sed@
--   installed; these are fairly common on GNU\/Linux systems but it
--   would be nice to make this more general.
loadAvg :: Logger
loadAvg = logCmd "/usr/bin/uptime | sed 's/.*: //; s/,//g'"

-- | Create a 'Logger' from an arbitrary shell command.
logCmd :: String -> Logger
logCmd c = io $ do (_, out, _, _) <- runInteractiveCommand c
                   fmap Just (hGetLine out) `E.catch` econst Nothing
                   -- no need to waitForProcess, we ignore SIGCHLD

-- | Get a count of filtered files in a directory.
-- See 'maildirUnread' and 'maildirNew' source for usage examples.
logFileCount :: FilePath          -- ^ directory in which to count files
             -> (String -> Bool)  -- ^ predicate to match if file should be counted
             -> Logger
logFileCount d p = do
    c <- liftIO ( getDirectoryContents d)
    let n = length $ Prelude.filter p c
    case n of
        0 -> return Nothing
        _ -> return $ Just $ show n

-- | Get a count of unread mails in a maildir. For maildir format
-- details, to write loggers for other classes of mail, see
-- <http://cr.yp.to/proto/maildir.html> and 'logFileCount'.
maildirUnread :: FilePath -> Logger
maildirUnread mdir = logFileCount (mdir ++ "/cur/") (isSuffixOf ",")

-- | Get a count of new mails in a maildir.
maildirNew :: FilePath -> Logger
maildirNew mdir = logFileCount (mdir ++ "/new/") (not . isPrefixOf ".")

-- $xmonad
--
-- A very small sample of what you can log since you have access to X. For
-- example you can loggerize the number of windows on each workspace, or
-- titles on other workspaces, or the id of the previously focused workspace....

-- | Get the title (name) of the focused window.
logTitle :: Logger
logTitle = withWindowSet $ traverse (fmap show . getName) . W.peek

-- | Get the name of the current layout.
logLayout :: Logger
logLayout = withWindowSet $ return . Just . ld
  where ld = description . W.layout . W.workspace . W.current

-- | Get the name of the current workspace.
logCurrent :: Logger
logCurrent = withWindowSet $ return . Just . W.currentTag

-- $format
-- Combine logger formatting functions to make your
-- 'XMonad.Hooks.DynamicLog.ppExtras' more colorful and readable.
-- (For convenience this module exports 'Control.Applicative.<$>' to
-- use instead of \'.\' or \'$\' in hard to read formatting lines.
-- For example:
--
-- > myLogHook = dynamicLogWithPP defaultPP {
-- >     -- skipped
-- >     , ppExtras = [lLoad, lTitle, logSp 3, wrapL "[" "]" $ date "%a %d %b"]
-- >     , ppOrder = \(ws,l,_,xs) -> [l,ws] ++ xs
-- >     }
-- >   where
-- >     -- lTitle = fixedWidthL AlignCenter "." 99 . dzenColorL "cornsilk3" "" . padL . shortenL 80 $ logTitle
-- >     -- or something like:
-- >     lTitle = fixedWidthL AlignCenter "." 99 <$> dzenColorL "cornsilk3" "" <$> padL . shortenL 80 $ logTitle
-- >
-- >     lLoad = dzenColorL "#6A5ACD" "" . wrapL loadIcon "   " . padL $ loadAvg
-- >     loadIcon = " ^i(/home/me/.dzen/icons/load.xbm)"
--
-- Note: When applying 'shortenL' or 'fixedWidthL' to logger strings
-- containing colors or other formatting commands, apply the formatting
-- /after/ the length adjustment, or include \"invisible\" characters
-- in the length specification, e.g. in the above \'^fg(cornsilk3)\' and
-- \'^fg()' yields 19 invisible and 80 visible characters.

-- | Use a string formatting function to edit a 'Logger' string.
-- For example, to create a tag function to prefix or label loggers,
-- as in \'tag: output\', use:
--
-- > tagL l = onLogger $ wrap (l ++ ": ") ""
-- >
-- >    tagL "bat" battery
-- >    tagL "load" loadAvg
--
-- If you already have a (String -> String) function you want to
-- apply to a logger:
--
-- > revL = onLogger trim
--
-- See formatting utility source code for more 'onLogger' usage examples.
onLogger :: (String -> String) -> Logger -> Logger
onLogger = fmap . fmap

-- | Wrap a logger's output in delimiters, unless it is @X (Nothing)@
-- or @X (Just \"\")@. Some examples:
--
-- >    wrapL " | " " | " (date "%a %d %b") -- ' | Tue 19 Feb | '
-- >
-- >    wrapL "bat: " "" battery            -- ' bat: battery_logger_output'
wrapL :: String -> String -> Logger -> Logger
wrapL l r = onLogger $ wrap l r

-- | Make a logger's output constant width by padding with the given string,
-- /even if the logger is/ @X (Nothing)@ /or/ @X (Just \"\")@. Useful to
-- reduce visual noise as a title logger shrinks and grows, to use a fixed
-- width for a logger that sometimes becomes Nothing, or even to create
-- fancy spacers or character based art effects.
--
-- It fills missing logger output with a repeated character like \".\",
-- \":\" or pattern, like \" -.-\". The cycling padding string is reversed on
-- the left of the logger output. This is mainly useful with AlignCenter.
fixedWidthL :: Align  -- ^ AlignCenter, AlignRight, or AlignLeft
            -> String -- ^ String to cycle to pad missing logger output
            -> Int    -- ^ Fixed length to output (including invisible formatting characters)
            -> Logger -> Logger
fixedWidthL a str n logger = do
    mbl <- logger
    let l = fromMaybe "" mbl
    case a of
       AlignCenter -> toL (take n $ padhalf l ++ l ++ cs)
       AlignRight -> toL (reverse (take n $ reverse l ++ cs))
       _ -> toL (take n $ l ++ cs)
  where
    toL = return . Just
    cs  = cycle str
    padhalf x = reverse $ take ((n - length x) `div` 2) cs

-- | Create a \"spacer\" logger, e.g. @logSp 3 -- loggerizes \'   \'@.
-- For more complex \"spacers\", use 'fixedWidthL' with @return Nothing@.
logSp :: Int -> Logger
logSp n = return . Just . take n $ cycle " "

-- | Pad a logger's output with a leading and trailing space, unless it
-- is @X (Nothing)@ or @X (Just \"\")@.
padL :: Logger -> Logger
padL = onLogger pad

-- | Limit a logger's length, adding \"...\" if truncated.
shortenL :: Int -> Logger -> Logger
shortenL = onLogger . shorten

-- | Color a logger's output with dzen foreground and background colors.
--
-- >  dzenColorL "green" "#2A4C3F" battery
dzenColorL :: String -> String -> Logger -> Logger
dzenColorL fg bg = onLogger $ dzenColor fg bg

-- | Color a logger's output with xmobar foreground and background colors.
--
-- >  xmobarColorL "#6A5ACD" "gray6" loadAverage
xmobarColorL :: String -> String -> Logger -> Logger
xmobarColorL fg bg = onLogger $ xmobarColor fg bg

-- todo
-- * dynamicLogXinerama logger? Or sorted onscreen Id's with "current" indicator?
-- is logCurrent really useful at all?
--
-- * ppVisible, etc. Resolve code dup. somehow. Refactor DynamicLog so can
-- be used for regular PP stuff /and/ loggers?
--
-- * fns for "ppExtras as a whole", combine loggers more nicely.
--
-- * parsers  to use with fixedWidthL to be smarter about invisible characters?