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
|
#+title: Interfacing with window managers
Listed below are ways to interface xmobar with your window manager of
choice.
* Property-based Logging
** =XMonadLog=
- Aliases to XMonadLog
- Displays information from xmonad's =_XMONAD_LOG=. You can use
this by using functions from the [[https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Hooks-DynamicLog.html][XMonad.Hooks.DynamicLog]]
module. By using the =xmonadPropLog= function in your logHook,
you can write the the above property. The following shows a
minimal xmonad configuration that spawns xmobar and then
writes to the =_XMONAD_LOG= property.
#+begin_src haskell
main = do
spawn "xmobar"
xmonad $ def
{ logHook = dynamicLogString defaultPP >>= xmonadPropLog
}
#+end_src
This plugin can be used as a sometimes more convenient
alternative to =StdinReader=. For instance, it allows you to
(re)start xmobar outside xmonad.
** =UnsafeXMonadLog=
- Aliases to UnsafeXMonadLog
- Displays any text received by xmobar on the =_XMONAD_LOG= atom.
- Will not do anything to the text received. This means you can pass
xmobar dynamic actions. Be careful to escape (using =<raw=…>=) or
remove tags from dynamic text that you pipe through to xmobar in this
way.
- Sample usage: Send the list of your workspaces, enclosed by actions
tags, to xmobar. This enables you to switch to a workspace when you
click on it in xmobar!
#+begin_src shell
<action=`xdotool key alt+1`>ws1</action> <action=`xdotool key alt+1`>ws2</action>
#+end_src
- If you use xmonad, It is advised that you still use =xmobarStrip= for
the =ppTitle= in your logHook:
#+begin_src haskell
myPP = defaultPP { ppTitle = xmobarStrip }
main = xmonad $ def
{ logHook = dynamicLogString myPP >>= xmonadPropLog
}
#+end_src
** =XPropertyLog PropName=
- Aliases to =PropName=
- Reads the X property named by =PropName= (a string) and displays its
value. The [[../examples/xmonadpropwrite.hs][examples/xmonadpropwrite.hs script]] in xmobar's distribution
can be used to set the given property from the output of any other
program or script.
** =UnsafeXPropertyLog PropName=
- Aliases to =PropName=
- Same as =XPropertyLog= but the input is not filtered to avoid
injection of actions (cf. =UnsafeXMonadLog=). The program writing the
value of the read property is responsible of performing any needed
cleanups.
** =NamedXPropertyLog PropName Alias=
- Aliases to =Alias=
- Same as =XPropertyLog= but a custom alias can be specified.
** =UnsafeNamedXPropertyLog PropName Alias=
- Aliases to =Alias=
- Same as =UnsafeXPropertyLog=, but a custom alias can be specified.
* Logging via Stdin
** =StdinReader=
- Aliases to StdinReader
- Displays any text received by xmobar on its standard input.
- Strips actions from the text received. This means you can't pass
dynamic actions via stdin. This is safer than =UnsafeStdinReader=
because there is no need to escape the content before passing it to
xmobar's standard input.
** =UnsafeStdinReader=
- Aliases to UnsafeStdinReader
- Displays any text received by xmobar on its standard input.
- Similar to [[=UnsafeXMonadLog=][UnsafeXMonadLog]], in the sense that it does not strip any
actions from the received text, only using =stdin= and not a property
atom of the root window. Please be equally carefully when using this
as when using =UnsafeXMonadLog=!
* Pipe-based Logging
** =PipeReader "default text:/path/to/pipe" Alias=
- Reads its displayed output from the given pipe.
- Prefix an optional default text separated by a colon
- Expands environment variables in the first argument of syntax =${VAR}=
or =$VAR=
** =MarqueePipeReader "default text:/path/to/pipe" (length, rate, sep) Alias=
- Generally equivalent to PipeReader
- Text is displayed as marquee with the specified length, rate in 10th
seconds and separator when it wraps around
#+begin_src haskell
Run MarqueePipeReader "/tmp/testpipe" (10, 7, "+") "mpipe"
#+end_src
- Expands environment variables in the first argument
** =BufferedPipeReader Alias [(Timeout, Bool, "/path/to/pipe1"), ..]=
- Display data from multiple pipes.
- Timeout (in tenth of seconds) is the value after which the
previous content is restored i.e. if there was already
something from a previous pipe it will be put on display
again, overwriting the current status.
- A pipe with Timeout of 0 will be displayed permanently, just
like =PipeReader=
- The boolean option indicates whether new data for this pipe
should make xmobar appear (unhide, reveal). In this case, the
Timeout additionally specifies when the window should be
hidden again. The output is restored in any case.
- Use it for OSD-like status bars e.g. for setting the volume or
brightness:
#+begin_src haskell
Run BufferedPipeReader "bpr"
[ ( 0, False, "/tmp/xmobar_window" )
, ( 15, True, "/tmp/xmobar_status" )
]
#+end_src
Have your window manager send window titles to
=/tmp/xmobar_window=. They will always be shown and not reveal
your xmobar. Sending some status information to
=/tmp/xmobar_status= will reveal xmonad for 1.5 seconds and
temporarily overwrite the window titles.
- Take a look at [[../examples/status.sh][examples/status.sh]]
- Expands environment variables for the pipe path
* Handle-based Logging
** =HandleReader Handle Alias=
- Display data from a Haskell =Handle=
- This plugin is only useful if you are running xmobar from another
Haskell program like XMonad.
- You can use =System.Process.createPipe= to create a pair of =read= &
=write= Handles. Pass the =read= Handle to HandleReader and write your
output to the =write= Handle:
#+begin_src haskell
(readHandle, writeHandle) <- createPipe
xmobarProcess <- forkProcess $ xmobar myConfig
{ commands =
Run (HandleReader readHandle "handle") : commands myConfig
}
hPutStr writeHandle "Hello World"
#+end_src
* Software Transactional Memory
When invoking xmobar from other Haskell code it can be easier and more
performant to use shared memory. The following plugins leverage
=Control.Concurrent.STM= to realize these gains for xmobar.
** =QueueReader (TQueue a) (a -> String) String=
- Display data from a Haskell =TQueue a=.
- This plugin is only useful if you are running xmobar from another
haskell program like xmonad.
- You should make an =IO= safe =TQueue a= with
=Control.Concurrent.STM.newTQueueIO=. Write to it from the user
code with =writeTQueue=, and read with =readTQueue=. A common use
is to overwite =ppOutput= from =XMonad.Hooks.DynamicLog= as shown
below.
#+begin_src haskell
main :: IO ()
main = do
initThreads
q <- STM.newTQueueIO @String
bar <- forkOS $ xmobar myConf
{ commands = Run (QueueReader q id "XMonadLog") : commands myConf }
xmonad $ def { logHook = logWorkspacesToQueue q }
logWorkspacesToQueue :: STM.TQueue String -> X ()
logWorkspacesToQueue q =
dynamicLogWithPP def { ppOutput = STM.atomically . STM.writeTQueue q }
#+end_src
Note that xmonad uses blocking Xlib calls in its event loop and isn't
normally compiled with
[[https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-concurrent.html][the threaded RTS]]
so an xmobar thread running inside xmonad will suffer from delayed
updates. It is thus necessary to enable =-threaded= when compiling
xmonad configuration (=xmonad.hs=), e.g. by using a custom
=~/.xmonad/build= script.
* Example of using the DBus IPC interface with XMonad
Bind the key which should {,un}map xmobar to a dummy value. This is
necessary for {,un}grabKey in xmonad.
#+begin_src haskell
((0, xK_Alt_L), pure ())
#+end_src
Also, install =avoidStruts= layout modifier from
=XMonad.Hooks.ManageDocks=
Finally, install these two event hooks (=handleEventHook= in =XConfig=)
=myDocksEventHook= is a replacement for =docksEventHook= which reacts
on unmap events as well (which =docksEventHook= doesn't).
#+begin_src haskell
import qualified XMonad.Util.ExtensibleState as XS
data DockToggleTime = DTT { lastTime :: Time } deriving (Eq, Show, Typeable)
instance ExtensionClass DockToggleTime where
initialValue = DTT 0
toggleDocksHook :: Int -> KeySym -> Event -> X All
toggleDocksHook to ks ( KeyEvent { ev_event_display = d
, ev_event_type = et
, ev_keycode = ekc
, ev_time = etime
} ) =
io (keysymToKeycode d ks) >>= toggleDocks >> return (All True)
where
toggleDocks kc
| ekc == kc && et == keyPress = do
safeSendSignal ["Reveal 0", "TogglePersistent"]
XS.put ( DTT etime )
| ekc == kc && et == keyRelease = do
gap <- XS.gets ( (-) etime . lastTime )
safeSendSignal [ "TogglePersistent"
, "Hide " ++ show (if gap < 400 then to else 0)
]
| otherwise = return ()
safeSendSignal s = catchX (io $ sendSignal s) (return ())
sendSignal = withSession . callSignal
withSession mc = connectSession >>= \c -> callNoReply c mc >> disconnect c
callSignal :: [String] -> MethodCall
callSignal s = ( methodCall
( objectPath_ "/org/Xmobar/Control" )
( interfaceName_ "org.Xmobar.Control" )
( memberName_ "SendSignal" )
) { methodCallDestination = Just $ busName_ "org.Xmobar.Control"
, methodCallBody = map toVariant s
}
toggleDocksHook _ _ _ = return (All True)
myDocksEventHook :: Event -> X All
myDocksEventHook e = do
when (et == mapNotify || et == unmapNotify) $
whenX ((not `fmap` (isClient w)) <&&> runQuery checkDock w) refresh
return (All True)
where w = ev_window e
et = ev_event_type e
#+end_src
|