diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,17 @@
+## Version 0.42 (March, 2022)
+
+_New features_
+
+  - New text mode (thanks to Pavel Kagulin, see issue #601), with
+    output formats compatible with color terminals and pango.
+  - New text mode with format following swaybar-protocol, supporting
+    colors, faces, boxes and actions.
+
+_Bug fixes_
+
+   - Fix for bottom placement (#608)
+   - Fix for memory leak during X drawing (#609)
+
 ## Version 0.41 (January, 2022)
 
 _New features_
diff --git a/doc/plugins.org b/doc/plugins.org
--- a/doc/plugins.org
+++ b/doc/plugins.org
@@ -1,1640 +1,1326 @@
-* System Monitor Plugins
-
-  This is the description of the system monitor plugins available in
-  xmobar. Some of them are only installed when an optional build
-  option is set: we mention that fact, when needed, in their
-  description.
-
-  Each monitor has an =alias= to be used in the output
-  template. Monitors may have default aliases, see the documentation
-  of the monitor in question.
-
-  There are two types of arguments: ones that all monitors share (the
-  so called /default monitor arguments/) and arguments that are specific
-  to a certain monitor.
-
-  All Monitors accept a common set of arguments, described below in
-  [[Default Monitor Arguments]]. Some monitors also accept additional
-  options that are specific to them. When specifying the list of
-  arguments in your configuration, the common options come first,
-  followed by =--=, followed by any monitor-specific options. For
-  example, the following [[=Battery Args RefreshRate=][Battery]] configuration first sets the global
-  =template= and =Low= arguments and then specifies the battery-specific
-  =off= option.
-
-  #+begin_src haskell
-    Run Battery
-      [ "--template", "<acstatus>"
-      , "--Low"     , "15"
-      -- battery specific options start here.
-      , "--"
-      , "--off"     , "<left> (<timeleft>)"
-      ]
-      100
-  #+end_src
-
-** Icon Patterns
-
-Some monitors allow usage of strings that depend on some integer value
-from 0 to 8 by replacing all occurrences of =%%= with it
-(i.e. =<icon=/path/to/icon_%%.xpm/>= will be interpreted as
-=<icon=/path/to/icon_3.xpm/>= when the value is =3=, also =%= is
-interpreted as =%=, =%%= as =3=, =%%%= as =3%=, =%%%%= as =33= and so
-on). Essentially it allows to replace vertical bars with custom
-icons. For example,
-
-#+begin_src haskell
-  Run Brightness
-    [ "-t", "<ipat>"
-    , "--"
-    , "--brightness-icon-pattern", "<icon=bright_%%.xpm/>"
-    ] 30
-#+end_src
-
-Will display =bright_0.xpm= to =bright_8.xpm= depending on current
-brightness value.
-
-** Default Monitor Arguments
-
-These are the options available for all monitors:
-
-- =-t= /string/ Output template
-
-  - Template for the monitor output. Field names must be enclosed
-    between pointy brackets (=<foo>=) and will be substituted by the
-    computed values. You can also specify the foreground (and
-    optionally, background) color for a region by bracketing it between
-    =<fc=fgcolor>= (or =<fc=fgcolor,bgcolor>=) and =</fc>=. The rest of
-    the template is output verbatim.
-  - Long option: =--template=
-  - Default value: per monitor (see above).
-
-- =-H= /number/ The high threshold.
-
-  - Numerical values higher than /number/ will be displayed with the
-    color specified by =-h= (see below).
-  - Long option: =--High=
-  - Default value: 66
-
-- =-L= /number/ The low threshold.
-
-  - Numerical values higher than /number/ and lower than the high
-    threshold will be displayed with the color specified by =-n= (see
-    below). Values lower than /number/ will use the =-l= color.
-  - Long option: =--Low=
-  - Default value: 33
-
-- =-h= /color/ High threshold color.
-
-  - Color for displaying values above the high threshold. /color/ can be
-    either a name (e.g. "blue") or an hexadecimal RGB (e.g. "#FF0000").
-  - Long option: =--high=
-  - Default: none (use the default foreground).
-
-- =-n= /color/ Color for 'normal' values
-
-  - Color used for values greater than the low threshold but lower than
-    the high one.
-  - Long option: =--normal=
-  - Default: none (use the default foreground).
-
-- =-l= /color/ The low threshold color
-
-  - Color for displaying values below the low threshold.
-  - Long option: =--low=
-  - Default: none (use the default foreground).
-
-- =-S= /boolean/ Display optional suffixes
-
-  - When set to a true designator ("True", "Yes" or "On"), optional
-    value suffixes such as the '%' symbol or optional units will be
-    displayed.
-  - Long option: =--suffix=
-  - Default: False.
-
-- =-p= /number/ Percentages padding
-
-  - Width, in number of digits, for quantities representing percentages.
-    For instance =-p 3= means that all percentages in the monitor will
-    be represented using 3 digits.
-  - Long option: =--ppad=
-  - Default value: 0 (don't pad)
-
-- =-d= /number/ Decimal digits
-
-  - Number of digits after the decimal period to use in float values.
-  - Long option: =--ddigits=
-  - Default value: 0 (display only integer part)
-
-- =-m= /number/ Minimum field width
-
-  - Minimum width, in number of characters, of the fields in the monitor
-    template. Values whose printed representation is shorter than this
-    value will be padded using the padding characters given by the =-c=
-    option with the alignment specified by =-a= (see below).
-  - Long option: =--minwidth=
-  - Default: 0
-
-- =-M= /number/ Maximum field width
-
-  - Maximum width, in number of characters, of the fields in the monitor
-    template. Values whose printed representation is longer than this
-    value will be truncated.
-  - Long option: =--maxwidth=
-  - Default: 0 (no maximum width)
-
-- =-e= /string/ Maximum width ellipsis
-
-  - Ellipsis to be added to the field when it has reached its max width.
-  - Long option: =--maxwidthellipsis=
-  - Default: "" (no ellipsis)
-
-- =-w= /number/ Fixed field width
-
-  - All fields will be set to this width, padding or truncating as
-    needed.
-  - Long option: =--width=
-  - Default: 0 (variable width)
-
-- =-T= /number/ Maximum total width
-
-  - Maximum total width of the text.
-  - Long option: =--maxtwidth=
-  - Default: 0 (no limit)
-
-- =-E= /string/ Maximum total width ellipsis
-
-  - Ellipsis to be added to the total text when it has reached its max
-    width.
-  - Long option: =--maxtwidthellipsis=
-  - Default: "" (no ellipsis)
-
-- =-c= /string/
-
-  - Characters used for padding. The characters of /string/ are used
-    cyclically. E.g., with =-P +- -w 6=, a field with value "foo" will
-    be represented as "+-+foo".
-  - Long option: =--padchars=
-  - Default value: " "
-
-- =-a= r|l Field alignment
-
-  - Whether to use right (r) or left (l) alignment of field values when
-    padding.
-  - Long option: =--align=
-  - Default value: r (padding to the left)
-
-- =-b= /string/ Bar background
-
-  - Characters used, cyclically, to draw the background of bars. For
-    instance, if you set this option to "·.", an empty bar will look
-    like this: =·.·.·.·.·.=
-  - Long option: =--bback=
-  - Default value: ":"
-
-- =-f= /string/ Bar foreground
-
-  - Characters used, cyclically, to draw the foreground of bars.
-  - Long option: =--bfore=
-  - Default value: "#"
-
-- =-W= /number/ Bar width
-
-  - Total number of characters used to draw bars.
-  - Long option: =--bwidth=
-  - Default value: 10
-  - Special value: 0. When this parameter is 0, the percentage to
-    display is interpreted as a position in the bar foreground string
-    (given by =-f=), and the character at that position is displayed.
-
-- =-x= /string/ N/A string
-
-  - String to be used when the monitor is not available
-  - Long option: =--nastring=
-  - Default value: "N/A"
-
-Commands' arguments must be set as a list. E.g.:
-
-#+begin_src haskell
-  Run Weather "EGPF" ["-t", "<station>: <tempC>C"] 36000
-#+end_src
-
-In this case xmobar will run the weather monitor, getting information
-for the weather station ID EGPF (Glasgow Airport, as a homage to GHC)
-every hour (36000 tenth of seconds), with a template that will output
-something like:
-
-#+begin_src shell
-  Glasgow Airport: 16.0C
-#+end_src
-
-** Battery Monitors
-*** =Battery Args RefreshRate=
-
- Same as
-
- #+begin_src haskell
-   BatteryP ["BAT", "BAT0", "BAT1", "BAT2"] Args RefreshRate
- #+end_src
-
-*** =BatteryP Dirs Args RefreshRate=
-
- - Aliases to =battery=
-
- - Dirs: list of directories in =/sys/class/power_supply/= where to look
-   for the ACPI files of each battery. Example: =["BAT0","BAT1","BAT2"]=.
-   Only up to 3 existing directories will be searched.
-
- - Args: default monitor arguments, plus the following specific ones
-   (these options, being specific to the monitor, are to be specified
-   after a =--= in the argument list):
-
-   - =-O=: string for AC "on" status (default: "On")
-   - =-i=: string for AC "idle" status (default: "On")
-   - =-o=: string for AC "off" status (default: "Off")
-   - =-L=: low power (=watts=) threshold (default: 10)
-   - =-H=: high power threshold (default: 12)
-   - =-l=: color to display power lower than the =-L= threshold
-   - =-m=: color to display power lower than the =-H= threshold
-   - =-h=: color to display power higher than the =-H= threshold
-   - =-p=: color to display positive power (battery charging)
-   - =-f=: file in =/sys/class/power_supply= with AC info (default:
-     "AC/online")
-   - =-A=: a number between 0 and 100, threshold below which the action
-     given by =-a=, if any, is performed (default: 5)
-   - =-a=: a string with a system command that is run when the percentage
-     left in the battery is less or equal than the threshold given by the
-     =-A= option. If not present, no action is undertaken.
-   - =-P=: to include a percentage symbol in =left=.
-   - =--on-icon-pattern=: dynamic string for current battery charge when
-     AC is "on" in =leftipat=.
-   - =--off-icon-pattern=: dynamic string for current battery charge when
-     AC is "off" in =leftipat=.
-   - =--idle-icon-pattern=: dynamic string for current battery charge
-     when AC is "idle" in =leftipat=.
-   - =--lows=: string for AC "off" status and power lower than the =-L=
-     threshold (default: "")
-   - =--mediums=: string for AC "off" status and power lower than the
-     =-H= threshold (default: "")
-   - =--highs=: string for AC "off" status and power higher than the =-H=
-     threshold (default: "")
-
- - Variables that can be used with the =-t/--template= argument:
-   =left=, =leftbar=, =leftvbar=, =leftipat=, =timeleft=, =watts=,
-   =acstatus=
-
- - Default template: =Batt: <watts>, <left>% / <timeleft>=
-
- - Example (note that you need "--" to separate regular monitor options
-   from Battery's specific ones):
-
-   #+begin_src haskell
-     Run BatteryP ["BAT0"]
-                  ["-t", "<acstatus><watts> (<left>%)",
-                   "-L", "10", "-H", "80", "-p", "3",
-                   "--", "-O", "<fc=green>On</fc> - ", "-i", "",
-                   "-L", "-15", "-H", "-5",
-                   "-l", "red", "-m", "blue", "-h", "green",
-                   "-a", "notify-send -u critical 'Battery running out!!'",
-                   "-A", "3"]
-                  600
-   #+end_src
-
-   In the above example, the thresholds before the =--= separator affect
-   only the =<left>= and =<leftbar>= fields, while those after the
-   separator affect how =<watts>= is displayed. For this monitor, neither
-   the generic nor the specific options have any effect on =<timeleft>=.
-   We are also telling the monitor to execute the unix command
-   =notify-send= when the percentage left in the battery reaches 6%.
-
-   It is also possible to specify template variables in the =-O= and =-o=
-   switches, as in the following example:
-
-   #+begin_src haskell
-     Run BatteryP ["BAT0"]
-                  ["-t", "<acstatus>"
-                  , "-L", "10", "-H", "80"
-                  , "-l", "red", "-h", "green"
-                  , "--", "-O", "Charging", "-o", "Battery: <left>%"
-                  ] 10
-   #+end_src
-
- - The "idle" AC state is selected whenever the AC power entering the
-   battery is zero.
-
-*** =BatteryN Dirs Args RefreshRate Alias=
-
- Works like =BatteryP=, but lets you specify an alias for the monitor
- other than "battery". Useful in case you one separate monitors for more
- than one battery.
-** Cpu and Memory Monitors
-*** =Cpu Args RefreshRate=
-
- - Aliases to =cpu=
- - Args: default monitor arguments, plus:
-
-   - =--load-icon-pattern=: dynamic string for cpu load in =ipat=
-
- - Thresholds refer to percentage of CPU load
- - Variables that can be used with the =-t/--template= argument:
-   =total=, =bar=, =vbar=, =ipat=, =user=, =nice=, =system=, =idle=,
-   =iowait=
- - Default template: =Cpu: <total>%=
-
-*** =MultiCpu Args RefreshRate=
-
- - Aliases to =multicpu=
- - Args: default monitor arguments, plus:
-
-   - =--load-icon-pattern=: dynamic string for overall cpu load in
-     =ipat=.
-   - =--load-icon-patterns=: dynamic string for each cpu load in
-     =autoipat=, =ipat{i}=. This option can be specified several times.
-     nth option corresponds to nth cpu.
-   - =--fallback-icon-pattern=: dynamic string used by =autoipat= and
-     =ipat{i}= when no =--load-icon-patterns= has been provided for
-     =cpu{i}=
-   - =--contiguous-icons=: flag (no value needs to be provided) that
-     causes the load icons to be drawn without padding.
-
- - Thresholds refer to percentage of CPU load
- - Variables that can be used with the =-t/--template= argument:
-   =autototal=, =autobar=, =autovbar=, =autoipat=, =autouser=,
-   =autonice=, =autosystem=, =autoidle=, =total=, =bar=, =vbar=, =ipat=,
-   =user=, =nice=, =system=, =idle=, =total0=, =bar0=, =vbar0=, =ipat0=,
-   =user0=, =nice0=, =system0=, =idle0=, ... The auto* variables
-   automatically detect the number of CPUs on the system and display one
-   entry for each.
- - Default template: =Cpu: <total>%=
-
-*** =CpuFreq Args RefreshRate=
-
- - Aliases to =cpufreq=
-
- - Args: default monitor arguments
-
- - Thresholds refer to frequency in GHz
-
- - Variables that can be used with the =-t/--template= argument:
-   =cpu0=, =cpu1=, .., =cpuN=
-
- - Default template: =Freq: <cpu0>GHz=
-
- - This monitor requires acpi_cpufreq module to be loaded in kernel
-
- - Example:
-
-   #+begin_src haskell
-     Run CpuFreq ["-t", "Freq:<cpu0>|<cpu1>GHz", "-L", "0", "-H", "2",
-                  "-l", "lightblue", "-n","white", "-h", "red"] 50
-   #+end_src
-
-*** =CoreTemp Args RefreshRate=
-
- - Aliases to =coretemp=
-
- - Args: default monitor arguments
-
- - Thresholds refer to temperature in degrees
-
- - Variables that can be used with the =-t/--template= argument:
-   =core0=, =core1=, .., =coreN=
-
- - Default template: =Temp: <core0>C=
-
- - This monitor requires coretemp module to be loaded in kernel
-
- - Example:
-
-   #+begin_src haskell
-     Run CoreTemp ["-t", "Temp:<core0>|<core1>C",
-                   "-L", "40", "-H", "60",
-                   "-l", "lightblue", "-n", "gray90", "-h", "red"] 50
-   #+end_src
-
-*** =MultiCoreTemp Args RefreshRate=
-
- - Aliases to =multicoretemp=
-
- - Args: default monitor arguments, plus:
-
-   - =--max-icon-pattern=: dynamic string for overall cpu load in
-     =maxipat=.
-   - =--avg-icon-pattern=: dynamic string for overall cpu load in
-     =avgipat=.
-   - =--mintemp=: temperature in degree Celsius, that sets the lower
-     limit for percentage calculation.
-   - =--maxtemp=: temperature in degree Celsius, that sets the upper
-     limit for percentage calculation.
-   - =--hwmon-path=: this monitor tries to find coretemp devices by
-     looking for them in directories following the pattern
-     =/sys/bus/platform/devices/coretemp.*/hwmon/hwmon*=, but some
-     processors (notably Ryzen) might expose those files in a different
-     tree (e.g., Ryzen) puts them somewhere in "/sys/class/hwmon/hwmon*",
-     and the lookup is most costly. With this option, it is possible to
-     explicitly specify the full path to the directory where the
-     =tempN_label= and =tempN_input= files are located.
-
- - Thresholds refer to temperature in degree Celsius
-
- - Variables that can be used with the =-t/--template= argument: =max=,
-   =maxpc=, =maxbar=, =maxvbar=, =maxipat=, =avg=, =avgpc=, =avgbar=,
-   =avgvbar=, =avgipat=, =core0=, =core1=, ..., =coreN=
-
-   The /pc, /bar, /vbar and /ipat variables are showing percentages on
-   the scale defined by =--mintemp= and =--maxtemp=. The max* and avg*
-   variables to the highest and the average core temperature.
-
- - Default template: =Temp: <max>°C - <maxpc>%=
-
- - This monitor requires coretemp module to be loaded in kernel
-
- - Example:
-
-   #+begin_src haskell
-     Run MultiCoreTemp ["-t", "Temp: <avg>°C | <avgpc>%",
-                        "-L", "60", "-H", "80",
-                        "-l", "green", "-n", "yellow", "-h", "red",
-                        "--", "--mintemp", "20", "--maxtemp", "100"] 50
-   #+end_src
-
-*** =K10Temp Slot Args RefreshRate=
-
- - Aliases to =k10temp=
-
- - Slot: The PCI slot address of the k10temp device as a string.  You
-   can find it as a subdirectory in =/sys/bus/pci/drivers/k10temp/=.
-
- - Args: default monitor arguments
-
- - Thresholds refer to temperature in degrees
-
- - Variables that can be used with the =-t/--template= argument:
-   =Tctl=, =Tdie=, =Tccd1=, .., =Tccd8=
-
- - Default template: =Temp: <Tdie>C=
-
- - This monitor requires k10temp module to be loaded in kernel
-
- - It is important to note that not all measurements are available
-   on on all models of processor. Of particular importance - Tdie
-   (used in the default template) may not be present on processors
-   prior to Zen (17h). Tctl, however, may be offset from the real
-   temperature and so is not used by default.
-
- - Example:
-
-   #+begin_src haskell
-     Run K10Temp "0000:00:18.3"
-                 ["-t", "Temp: <Tdie>C|<Tccd1>C",
-                  "-L", "40", "-H", "60",
-                  "-l", "lightblue", "-n", "gray90", "-h", "red"]
-                 50
-   #+end_src
-
-
-*** =Memory Args RefreshRate=
-
- - Aliases to =memory=
- - Args: default monitor arguments, plus:
-
-   - =--used-icon-pattern=: dynamic string for used memory ratio in
-     =usedipat=.
-   - =--free-icon-pattern=: dynamic string for free memory ratio in
-     =freeipat=.
-   - =--available-icon-pattern=: dynamic string for available memory
-     ratio in =availableipat=.
-
- - Thresholds refer to percentage of used memory
- - Variables that can be used with the =-t/--template= argument:
-   =total=, =free=, =buffer=, =cache=, =available=, =used=, =usedratio=,
-   =usedbar=, =usedvbar=, =usedipat=, =freeratio=, =freebar=, =freevbar=,
-   =freeipat=, =availableratio=, =availablebar=, =availablevbar=,
-   =availableipat=
- - Default template: =Mem: <usedratio>% (<cache>M)=
-
-*** =Swap Args RefreshRate=
-
- - Aliases to =swap=
- - Args: default monitor arguments
- - Thresholds refer to percentage of used swap
- - Variables that can be used with the =-t/--template= argument:
-   =total=, =used=, =free=, =usedratio=
- - Default template: =Swap: <usedratio>%=
-** Date Monitors
-*** =Date Format Alias RefreshRate=
-
- - Format is a time format string, as accepted by the standard ISO C
-   =strftime= function (or Haskell's =formatCalendarTime=).  Basically,
-   if =date +"my-string"= works with your command then =Date= will handle
-   it correctly.
-
- - Timezone changes are picked up automatically every minute.
-
- - Sample usage:
-
-   #+begin_src haskell
-     Run Date "%a %b %_d %Y <fc=#ee9a00>%H:%M:%S</fc>" "date" 10
-   #+end_src
-
-*** =DateZone Format Locale Zone Alias RefreshRate=
-
- A variant of the =Date= monitor where one is able to explicitly set the
- time-zone, as well as the locale.
-
- - The format of =DateZone= is exactly the same as =Date=.
-
- - If =Locale= is =""= (the empty string) the default locale of the
-   system is used, otherwise use the given locale. If there are more
-   instances of =DateZone=, using the empty string as input for =Locale=
-   is not recommended.
-
- - =Zone= is the name of the =TimeZone=. It is assumed that the time-zone
-   database is stored in =/usr/share/zoneinfo/=. If the empty string is
-   given as =Zone=, the default system time is used.
-
- - Sample usage:
-
-   #+begin_src haskell
-     Run DateZone "%a %H:%M:%S" "de_DE.UTF-8" "Europe/Vienna" "viennaTime" 10
-   #+end_src
-** Disk Monitors
-*** =DiskU Disks Args RefreshRate=
-
- - Aliases to =disku=
-
- - Disks: list of pairs of the form (device or mount point, template),
-   where the template can contain =<size>=, =<free>=, =<used>=, =<freep>=
-   or =<usedp>=, =<freebar>=, =<freevbar>=, =<freeipat>=, =<usedbar>=,
-   =<usedvbar>= or =<usedipat>= for total, free, used, free percentage
-   and used percentage of the given file system capacity.
-
- - Thresholds refer to usage percentage.
-
- - Args: default monitor arguments. =-t/--template= is ignored. Plus
-
-   - =--free-icon-pattern=: dynamic string for free disk space in
-     =freeipat=.
-   - =--used-icon-pattern=: dynamic string for used disk space in
-     =usedipat=.
-
- - Default template: none (you must specify a template for each file
-   system).
-
- - Example:
-
-   #+begin_src haskell
-     DiskU [("/", "<used>/<size>"), ("sdb1", "<usedbar>")]
-           ["-L", "20", "-H", "50", "-m", "1", "-p", "3"]
-           20
-   #+end_src
-
-*** =DiskIO Disks Args RefreshRate=
-
- - Aliases to =diskio=
-
- - Disks: list of pairs of the form (device or mount point, template),
-   where the template can contain =<total>=, =<read>=, =<write>= for
-   total, read and write speed, respectively, as well as =<totalb>=,
-   =<readb>=, =<writeb>=, which report number of bytes during the last
-   refresh period rather than speed. There are also bar versions of each:
-   =<totalbar>=, =<totalvbar>=, =<totalipat>=, =<readbar>=, =<readvbar>=,
-   =<readipat>=, =<writebar>=, =<writevbar>=, and =<writeipat>=; and
-   their "bytes" counterparts: =<totalbbar>=, =<totalbvbar>=,
-   =<totalbipat>=, =<readbbar>=, =<readbvbar>=, =<readbipat>=,
-   =<writebbar>=, =<writebvbar>=, and =<writebipat>=.
-
- - Thresholds refer to speed in b/s
-
- - Args: default monitor arguments. =-t/--template= is ignored. Plus
-
-   - =--total-icon-pattern=: dynamic string for total disk I/O in
-     =<totalipat>=.
-   - =--write-icon-pattern=: dynamic string for write disk I/O in
-     =<writeipat>=.
-   - =--read-icon-pattern=: dynamic string for read disk I/O in
-     =<readipat>=.
-
- - Default template: none (you must specify a template for each file
-   system).
-
- - Example:
-
-   #+begin_src haskell
-     DiskIO [("/", "<read> <write>"), ("sdb1", "<total>")] [] 10
-   #+end_src
-
-** Keyboard Monitors
-*** =Kbd Opts=
-
- - Registers to XKB/X11-Events and output the currently active keyboard
-   layout. Supports replacement of layout names.
-
- - Aliases to =kbd=
-
- - Opts is a list of tuples:
-
-   - first element of the tuple is the search string
-   - second element of the tuple is the corresponding replacement
-
- - Example:
-
-   #+begin_src haskell
-     Run Kbd [("us(dvorak)", "DV"), ("us", "US")]
-   #+end_src
-
-*** =Locks=
-
- - Displays the status of Caps Lock, Num Lock and Scroll Lock.
-
- - Aliases to =locks=
-
- - Example:
-
-   #+begin_src haskell
-     Run Locks
-   #+end_src
-** Process Monitors
-*** =TopProc Args RefreshRate=
-
- - Aliases to =top=
- - Args: default monitor arguments. The low and high thresholds (=-L= and
-   =-H=) denote, for memory entries, the percent of the process memory
-   over the total amount of memory currently in use and, for cpu entries,
-   the activity percentage (i.e., the value of =cpuN=, which takes values
-   between 0 and 100).
- - Variables that can be used with the =-t/--template= argument: =no=,
-   =name1=, =cpu1=, =both1=, =mname1=, =mem1=, =mboth1=, =name2=, =cpu2=,
-   =both2=, =mname2=, =mem2=, =mboth2=, ...
- - Default template: =<both1>=
- - Displays the name and cpu/mem usage of running processes (=bothn= and
-   =mboth= display both, and is useful to specify an overall maximum
-   and/or minimum width, using the =-m/-M= arguments. =no= gives the
-   total number of processes.
-
-*** =TopMem Args RefreshRate=
-
- - Aliases to =topmem=
- - Args: default monitor arguments. The low and high thresholds (=-L= and
-   =-H=) denote the percent of the process memory over the total amount
-   of memory currently in use.
- - Variables that can be used with the =-t/--template= argument:
-   =name1=, =mem1=, =both1=, =name2=, =mem2=, =both2=, ...
- - Default template: =<both1>=
- - Displays the name and RSS (resident memory size) of running processes
-   (=bothn= displays both, and is useful to specify an overall maximum
-   and/or minimum width, using the =-m/-M= arguments.
-** Thermal Monitors
-*** =ThermalZone Number Args RefreshRate=
-
- - Aliases to "thermaln": so =ThermalZone 0 []= can be used in template
-   as =%thermal0%=
-
- - Thresholds refer to temperature in degrees
-
- - Args: default monitor arguments
-
- - Variables that can be used with the =-t/--template= argument: =temp=
-
- - Default template: =<temp>C=
-
- - This plugin works only on systems with devices having thermal zone.
-   Check directories in =/sys/class/thermal= for possible values of the
-   zone number (e.g., 0 corresponds to =thermal_zone0= in that
-   directory).
-
- - Example:
-
-   #+begin_src haskell
-     Run ThermalZone 0 ["-t","<id>: <temp>C"] 30
-   #+end_src
-
-*** =Thermal Zone Args RefreshRate=
-
- - *This plugin is deprecated. Use =ThermalZone= instead.*
-
- - Aliases to the Zone: so =Thermal "THRM" []= can be used in template as
-   =%THRM%=
-
- - Args: default monitor arguments
-
- - Thresholds refer to temperature in degrees
-
- - Variables that can be used with the =-t/--template= argument: =temp=
-
- - Default template: =Thm: <temp>C=
-
- - This plugin works only on systems with devices having thermal zone.
-   Check directories in /proc/acpi/thermal_zone for possible values.
-
- - Example:
-
-   #+begin_src haskell
-     Run Thermal "THRM" ["-t","iwl4965-temp: <temp>C"] 50
-   #+end_src
-** Volume Monitors
-*** =Volume Mixer Element Args RefreshRate=
-
- - Aliases to the mixer name and element name separated by a colon. Thus,
-   =Volume "default" "Master" [] 10= can be used as =%default:Master%=.
- - Args: default monitor arguments. Also accepts:
-
-   - =-O= /string/ On string
-
-     - The string used in place of =<status>= when the mixer element is
-       on. Defaults to "[on]".
-     - Long option: =--on=
-
-   - =-o= /string/ Off string
-
-     - The string used in place of =<status>= when the mixer element is
-       off. Defaults to "[off]".
-     - Long option: =--off=
-
-   - =-C= /color/ On color
-
-     - The color to be used for =<status>= when the mixer element is on.
-       Defaults to "green".
-     - Long option: =--onc=
-
-   - =-c= /color/ Off color
-
-     - The color to be used for =<status>= when the mixer element is off.
-       Defaults to "red".
-     - Long option: =--offc=
-
-   - =--highd= /number/ High threshold for dB. Defaults to -5.0.
-   - =--lowd= /number/ Low threshold for dB. Defaults to -30.0.
-   - =--volume-icon-pattern= /string/ dynamic string for current volume
-     in =volumeipat=.
-   - =-H= /number/ High threshold for volume (in %). Defaults to 60.0.
-
-     - Long option: =--highv=
-
-   - =-L= /number/ Low threshold for volume (in %). Defaults to 20.0.
-
-     - Long option: =--lowv=
-
-   - =-h=: /string/ High string
-
-     - The string added in front of =<status>= when the mixer element is
-       on and the volume percentage is higher than the =-H= threshold.
-       Defaults to "".
-     - Long option: =--highs=
-
-   - =-m=: /string/ Medium string
-
-     - The string added in front of =<status>= when the mixer element is
-       on and the volume percentage is lower than the =-H= threshold.
-       Defaults to "".
-     - Long option: =--mediums=
-
-   - =-l=: /string/ Low string
-
-     - The string added in front of =<status>= when the mixer element is
-       on and the volume percentage is lower than the =-L= threshold.
-       Defaults to "".
-     - Long option: =--lows=
-
- - Variables that can be used with the =-t/--template= argument:
-   =volume=, =volumebar=, =volumevbar=, =volumeipat=, =dB=, =status=,
-   =volumestatus=
- - Note that =dB= might only return 0 on your system. This is known to
-   happen on systems with a pulseaudio backend.
- - Default template: =Vol: <volume>% <status>=
- - Requires the package [[http://hackage.haskell.org/package/alsa-core][alsa-core]] and [[http://hackage.haskell.org/package/alsa-mixer][alsa-mixer]] installed in your
-   system. In addition, to activate this plugin you must pass the
-   =with_alsa= flag during compilation.
-
-*** =Alsa Mixer Element Args=
-
- Like [[=Volume Mixer Element Args RefreshRate=][Volume]] but with the following differences:
-
- - Uses event-based refreshing via =alsactl monitor= instead of polling,
-   so it will refresh instantly when there's a volume change, and won't
-   use CPU until a change happens.
- - Aliases to =alsa:= followed by the mixer name and element name
-   separated by a colon. Thus, =Alsa "default" "Master" []= can be used
-   as =%alsa:default:Master%=.
- - Additional options (after the =--=):
-   - =--alsactl=/path/to/alsactl=: If this option is not specified,
-     =alsactl= will be sought in your =PATH= first, and failing that, at
-     =/usr/sbin/alsactl= (this is its location on Debian systems.
-     =alsactl monitor= works as a non-root user despite living in
-     =/usr/sbin=.).
-   - =stdbuf= (from coreutils) must be (and most probably already is) in
-     your =PATH=.
-** Mail Monitors
-*** =Mail Args Alias=
-
- - Args: list of maildirs in form =[("name1","path1"),...]=. Paths may
-   start with a '~' to expand to the user's home directory.
-
- - This plugin requires inotify support in your Linux kernel and the
-   [[http://hackage.haskell.org/package/hinotify/][hinotify]] package. To activate, pass the =with_inotify= flag during
-   compilation.
-
- - Example:
-
-   #+begin_src haskell
-     Run Mail [("inbox", "~/var/mail/inbox"),
-               ("lists", "~/var/mail/lists")]
-              "mail"
-   #+end_src
-
-*** =MailX Args Opts Alias=
-
- - Args: list of maildirs in form =[("name1","path1","color1"),...]=.
-   Paths may start with a '~' to expand to the user's home directory.
-   When mails are present, counts are displayed with the given name and
-   color.
-
- - Opts is a possibly empty list of options, as flags. Possible values:
-   -d dir --dir dir a string giving the base directory where maildir
-   files with a relative path live. -p prefix --prefix prefix a string
-   giving a prefix for the list of displayed mail counts -s suffix
-   --suffix suffix a string giving a suffix for the list of displayed
-   mail counts
-
- - This plugin requires inotify support in your Linux kernel and the
-   [[http://hackage.haskell.org/package/hinotify/][hinotify]] package. To activate, pass the =with_inotify= flag during
-   compilation.
-
- - Example:
-
-   #+begin_src haskell
-     Run MailX [("I", "inbox", "green"),
-                ("L", "lists", "orange")]
-               ["-d", "~/var/mail", "-p", " ", "-s", " "]
-               "mail"
-   #+end_src
-
-*** =MBox Mboxes Opts Alias=
-
- - Mboxes a list of mbox files of the form =[("name", "path", "color")]=,
-   where name is the displayed name, path the absolute or relative (to
-   BaseDir) path of the mbox file, and color the color to use to display
-   the mail count (use an empty string for the default).
-
- - Opts is a possibly empty list of options, as flags. Possible values:
-   -a --all (no arg) Show all mailboxes, even if empty. -u (no arg) Show
-   only the mailboxes' names, sans counts. -d dir --dir dir a string
-   giving the base directory where mbox files with a relative path live.
-   -p prefix --prefix prefix a string giving a prefix for the list of
-   displayed mail counts -s suffix --suffix suffix a string giving a
-   suffix for the list of displayed mail counts
-
- - Paths may start with a '~' to expand to the user's home directory.
-
- - This plugin requires inotify support in your Linux kernel and the
-   [[http://hackage.haskell.org/package/hinotify/][hinotify]] package. To activate, pass the =with_inotify= flag during
-   compilation.
-
- - Example. The following command look for mails in =/var/mail/inbox= and
-   =~/foo/mbox=, and will put a space in front of the printed string
-   (when it's not empty); it can be used in the template with the alias
-   =mbox=:
-
-   #+begin_src haskell
-     Run MBox [("I ", "inbox", "red"), ("O ", "~/foo/mbox", "")]
-              ["-d", "/var/mail/", "-p", " "] "mbox"
-   #+end_src
-
-*** =NotmuchMail Alias Args Rate=
-
- This plugin checks for new mail, provided that this mail is indexed by
- =notmuch=. In the =notmuch= spirit, this plugin checks for new *threads*
- and not new individual messages.
-
- - Alias: What name the plugin should have in your template string.
-
- - Args: A list of =MailItem= s of the form
-
-   #+begin_src haskell
-     [ MailItem "name" "address" "query"
-     ...
-     ]
-   #+end_src
-
-   where
-
-   - =name= is what gets printed in the status bar before the number of
-     new threads.
-   - =address= is the e-mail address of the recipient, i.e. we only query
-     mail that was send to this particular address (in more concrete
-     terms, we pass the address to the =to:= constructor when performing
-     the search). If =address= is empty, we search through all unread
-     mail, regardless of whom it was sent to.
-   - =query= is funneled to =notmuch search= verbatim. For the general
-     query syntax, consult =notmuch search --help=, as well as
-     =notmuch-search-terms(7)=. Note that the =unread= tag is *always*
-     added in front of the query and composed with it via an *and*.
-
- - Rate: Rate with which to update the plugin (in deciseconds).
-
- - Example:
-
-   - A single =MailItem= that displays all unread threads from the given
-     address:
-
-     #+begin_src haskell
-       MailItem "mbs:" "soliditsallgood@mailbox.org" ""
-     #+end_src
-
-   - A single =MailItem= that displays all unread threads with
-     "[My-Subject]" somewhere in the title:
-
-     #+begin_src haskell
-       MailItem "S:" "" "subject:[My-Subject]"
-     #+end_src
-
-   - A full example of a =NotmuchMail= configuration:
-
-     #+begin_src haskell
-       Run NotmuchMail "mail"  -- name for the template string
-         [ -- All unread mail to the below address, but nothing that's tagged
-           -- with @lists@ or @haskell@.
-           MailItem "mbs:"
-                    "soliditsallgood@mailbox.org"
-                    "not tag:lists and not tag:haskell"
-
-           -- All unread mail that has @[Haskell-Cafe]@ in the subject line.
-         , MailItem "C:" "" "subject:[Haskell-Cafe]"
-
-           -- All unread mail that's tagged as @lists@, but not @haskell@.
-         , MailItem "H:" "" "tag:lists and not tag:haskell"
-         ]
-         600                   -- update every 60 seconds
-     #+end_src
-
-** Music Monitors
-*** =MPD Args RefreshRate=
-
- - This monitor will only be compiled if you ask for it using the
-   =with_mpd= flag. It needs [[http://hackage.haskell.org/package/libmpd/][libmpd]] 5.0 or later (available on Hackage).
-
- - Aliases to =mpd=
-
- - Args: default monitor arguments. In addition you can provide =-P=,
-   =-S= and =-Z=, with an string argument, to represent the playing,
-   stopped and paused states in the =statei= template field. The
-   environment variables =MPD_HOST= and =MPD_PORT= are used to configure
-   the mpd server to communicate with, unless given in the additional
-   arguments =-p= (=--port=) and =-h= (=--host=). Also available:
-
-   - =lapsed-icon-pattern=: dynamic string for current track position in
-     =ipat=.
-
- - Variables that can be used with the =-t/--template= argument: =bar=,
-   =vbar=, =ipat=, =state=, =statei=, =volume=, =length=, =lapsed=,
-   =remaining=, =plength= (playlist length), =ppos= (playlist position),
-   =flags= (ncmpcpp-style playback mode), =name=, =artist=, =composer=,
-   =performer=, =album=, =title=, =track=, =file=, =genre=, =date=
-
- - Default template: =MPD: <state>=
-
- - Example (note that you need "--" to separate regular monitor options
-   from MPD's specific ones):
-
-   #+begin_src haskell
-     Run MPD ["-t",
-              "<composer> <title> (<album>) <track>/<plength> <statei> [<flags>]",
-              "--", "-P", ">>", "-Z", "|", "-S", "><"] 10
-   #+end_src
-
-*** =MPDX Args RefreshRate Alias=
-
- Like =MPD= but uses as alias its last argument instead of "mpd".
-
-*** =Mpris1 PlayerName Args RefreshRate=
-
- - Aliases to =mpris1=
-
- - Requires [[http://hackage.haskell.org/package/dbus][dbus]] and [[http://hackage.haskell.org/package/text][text]] packages. To activate, pass the =with_mpris=
-   flag during compilation.
-
- - PlayerName: player supporting MPRIS v1 protocol. Some players need
-   this to be an all lowercase name (e.g. "spotify"), but some others
-   don't.
-
- - Args: default monitor arguments.
-
- - Variables that can be used with the =-t/--template= argument:
-   =album=, =artist=, =arturl=, =length=, =title=, =tracknumber=
-
- - Default template: =<artist> - <title>=
-
- - Example:
-
-   #+begin_src haskell
-     Run Mpris1 "clementine" ["-t", "<artist> - [<tracknumber>] <title>"] 10
-   #+end_src
-
-*** =Mpris2 PlayerName Args RefreshRate=
-
- - Aliases to =mpris2=
-
- - Requires [[http://hackage.haskell.org/package/dbus][dbus]] and [[http://hackage.haskell.org/package/text][text]] packages. To activate, pass the =with_mpris=
-   flag during compilation.
-
- - PlayerName: player supporting MPRIS v2 protocol. Some players need
-   this to be an all lowercase name (e.g. "spotify"), but some others
-   don't.
-
- - Args: default monitor arguments.
-
- - Variables that can be used with the =-t/--template= argument:
-   =album=, =artist=, =arturl=, =length=, =title=, =tracknumber=,
-   =composer=, =genre=
-
- - Default template: =<artist> - <title>=
-
- - Example:
-
-   #+begin_src haskell
-     Run Mpris2 "spotify" ["-t", "<artist> - [<composer>] <title>"] 10
-   #+end_src
-** Network Monitors
-*** =Network Interface Args RefreshRate=
-
- - Aliases to the interface name: so =Network "eth0" []= can be used as
-   =%eth0%=
- - Thresholds refer to velocities expressed in Kb/s
- - Args: default monitor arguments, plus:
-
-   - =--rx-icon-pattern=: dynamic string for reception rate in =rxipat=.
-   - =--tx-icon-pattern=: dynamic string for transmission rate in
-     =txipat=.
-   - =--up=: string used for the =up= variable value when the interface
-     is up.
-
- - Variables that can be used with the =-t=/=--template= argument: =dev=,
-   =rx=, =tx=, =rxbar=, =rxvbar=, =rxipat=, =txbar=, =txvbar=, =txipat=,
-   =up=. Reception and transmission rates (=rx= and =tx=) are displayed
-   by default as Kb/s, without any suffixes, but you can set the =-S= to
-   "True" to make them displayed with adaptive units (Kb/s, Mb/s, etc.).
- - Default template: =<dev>: <rx>KB|<tx>KB=
-
-*** =DynNetwork Args RefreshRate=
-
- - Active interface is detected automatically
- - Aliases to "dynnetwork"
- - Thresholds are expressed in Kb/s
- - Args: default monitor arguments, plus:
-
- - =--rx-icon-pattern=: dynamic string for reception rate in =rxipat=.
- - =--tx-icon-pattern=: dynamic string for transmission rate in =txipat=
- - =--devices=: comma-separated list of devices to show.
-
- - Variables that can be used with the =-t=/=--template= argument:
-   =dev=, =rx=, =tx=, =rxbar=, =rxvbar=, =rxipat=, =txbar=, =txvbar=,
-   =txipat=.
-
- Reception and transmission rates (=rx= and =tx=) are displayed in Kbytes
- per second, and you can set the =-S= to "True" to make them displayed
- with units (the string "Kb/s").
- - Default template: =<dev>: <rx>KB|<tx>KB=
- - Example of usage of =--devices= option:
-
-     =["--", "--devices", "wlp2s0,enp0s20f41"]=
-
-*** =Wireless Interface Args RefreshRate=
-
- - If set to "", first suitable wireless interface is used.
- - Aliases to the interface name with the suffix "wi": thus,
-   =Wireless   "wlan0" []= can be used as =%wlan0wi%=, and
-   =Wireless "" []= as =%wi%=.
- - Args: default monitor arguments, plus:
-
-   - =--quality-icon-pattern=: dynamic string for connection quality in
-     =qualityipat=.
-
- - Variables that can be used with the =-t=/=--template= argument:
-   =ssid=, =signal=, =quality=, =qualitybar=, =qualityvbar=,
-   =qualityipat=
- - Thresholds refer to link quality on a =[0, 100]= scale. Note that
-   =quality= is calculated from =signal= (in dBm) by a possibly lossy
-   conversion. It is also not taking into account many factors such as
-   noise level, air busy time, transcievers' capabilities and the others
-   which can have drastic impact on the link performance.
- - Default template: =<ssid> <quality>=
- - To activate this plugin you must pass the =with_nl80211= or the
-   =with_iwlib= flag during compilation.
-** Weather Monitors
-*** =Weather StationID Args RefreshRate=
-
- - Aliases to the Station ID: so =Weather "LIPB" []= can be used in
-   template as =%LIPB%=
- - Thresholds refer to temperature in the selected units
- - Args: default monitor arguments, plus:
-
-   - =--weathers= /string/ : display a default string when the =weather=
-     variable is not reported.
-
-     - short option: =-w=
-     - Default: ""
-
-   - =--useManager= /bool/ : Whether to use one single manager per
-     monitor for managing network connections or create a new one every
-     time a connection is made.
-
-     - Short option: =-m=
-     - Default: True
-
- - Variables that can be used with the =-t/--template= argument:
-   =station=, =stationState=, =year=, =month=, =day=, =hour=,
-   =windCardinal=, =windAzimuth=, =windMph=, =windKnots=, =windMs=,
-   =windKmh= =visibility=, =skyCondition=, =weather=, =tempC=, =tempF=,
-   =dewPointC=, =dewPointF=, =rh=, =pressure=
- - Default template: =<station>: <tempC>C, rh <rh>% (<hour>)=
- - Retrieves weather information from http://tgftp.nws.noaa.gov. Here is
-   an [[https://tgftp.nws.noaa.gov/data/observations/metar/decoded/CYLD.TXT][example]], also showcasing the kind of information that may be
-   extracted. Here is [[https://weather.rap.ucar.edu/surface/stations.txt][a sample list of station IDs]].
-
-*** =WeatherX StationID SkyConditions Args RefreshRate=
-
- - Works in the same way as =Weather=, but takes an additional argument,
-   a list of pairs from sky conditions to their replacement (typically a
-   unicode string or an icon specification).
- - Use the variable =skyConditionS= to display the replacement of the
-   corresponding sky condition. All other =Weather= template variables
-   are available as well.
-
- For example:
-
- #+begin_src haskell
-   WeatherX "LEBL"
-            [ ("clear", "🌣")
-            , ("sunny", "🌣")
-            , ("mostly clear", "🌤")
-            , ("mostly sunny", "🌤")
-            , ("partly sunny", "⛅")
-            , ("fair", "🌑")
-            , ("cloudy","☁")
-            , ("overcast","☁")
-            , ("partly cloudy", "⛅")
-            , ("mostly cloudy", "🌧")
-            , ("considerable cloudiness", "⛈")]
-            ["-t", "<fn=2><skyConditionS></fn> <tempC>° <rh>%  <windKmh> (<hour>)"
-            , "-L","10", "-H", "25", "--normal", "black"
-            , "--high", "lightgoldenrod4", "--low", "darkseagreen4"]
-            18000
- #+end_src
-
- As mentioned, the replacement string can also be an icon specification,
- such as =("clear", "<icon=weather-clear.xbm/>")=.
-** Other Monitors
-*** =Brightness Args RefreshRate=
-
- - Aliases to =bright=
-
- - Args: default monitor arguments, plus the following specif ones:
-
-   - =-D=: directory in =/sys/class/backlight/= with files in it
-     (default: "acpi_video0")
-   - =-C=: file with the current brightness (default: actual_brightness)
-   - =-M=: file with the maximum brightness (default: max_brightness)
-   - =--brightness-icon-pattern=: dynamic string for current brightness
-     in =ipat=.
-
- - Variables that can be used with the =-t/--template= argument:
-   =vbar=, =percent=, =bar=, =ipat=
-
- - Default template: =<percent>=
-
- - Example:
-
-   #+begin_src haskell
-     Run Brightness ["-t", "<bar>"] 60
-   #+end_src
-
-*** =CatInt n filename=
-
- - Reads and displays an integer from the file whose path is =filename=
-   (especially useful with files in =/sys=).
-
- - Aliases as =catn= (e.g. =Cat 0= as =cat0=, etc.) so you can have
-   several.
-
- - Example:
-
-   #+begin_src haskell
-     Run CatInt 0 "/sys/devices/platform/thinkpad_hwmon/fan1_input" [] 50
-   #+end_src
-
-*** =CommandReader "/path/to/program" Alias=
-
- - Runs the given program, and displays its standard output.
-
-*** =Uptime Args RefreshRate=
-
- - Aliases to =uptime=
- - Args: default monitor arguments. The low and high thresholds refer to
-   the number of days.
- - Variables that can be used with the =-t/--template= argument: =days=,
-   =hours=, =minutes=, =seconds=. The total uptime is the sum of all
-   those fields. You can set the =-S= argument to =True= to add units to
-   the display of those numeric fields.
- - Default template: =Up: <days>d <hours>h <minutes>m=
-
-*** =UVMeter=
-
- - Aliases to "uv" + station id. For example: =%uv Brisbane%= or
-   =%uv   Alice Springs%=
-
- - Args: default monitor arguments, plus:
-
-   - =--useManager= /bool/ : Whether to use one single manager per
-     monitor for managing network connections or create a new one every
-     time a connection is made.
-
-     - Short option: =-m=
-     - Default: True
-
- - /Reminder:/ Keep the refresh rate high, to avoid making unnecessary
-   requests every time the plug-in is run.
-
- - Station IDs can be found here:
-   http://www.arpansa.gov.au/uvindex/realtime/xml/uvvalues.xml
-
- - Example:
-
-   #+begin_src haskell
-     Run UVMeter "Brisbane" ["-H", "3", "-L", "3", "--low", "green", "--high", "red"] 900
-   #+end_src
-
-* 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 [[https://github.com/jaor/xmobar/raw/master/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 [[http://github.com/jaor/xmobar/raw/master/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
-       q <- STM.newTQueueIO @String
-       bar <- forkIO $ 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 }
-       where
-         -- Manage the PrettyPrinting configuration here.
-         ppLayout' :: String -> String
-         ppLayout' "Spacing Tall"        = xpm "layout-spacing-tall"
-         ppLayout' "Spacing Mirror Tall" = xpm "layout-spacing-mirror"
-         ppLayout' "Spacing Full"        = xpm "layout-full"
-         ppLayout' x = x
-
-         icon :: String -> String
-         icon path = "<icon=" ++ path ++ "/>"
-
-         xpm :: String -> String
-         xpm = icon . (++ ".xpm")
-   #+end_src
-
-* Executing External Commands
-
-  In order to execute an external command you can either write the
-  command name in the template, in this case it will be executed
-  without arguments, or you can configure it in the "commands"
-  configuration option list with the Com template command:
-
-  =Com ProgramName Args Alias RefreshRate=
-
-  - ProgramName: the name of the program
-  - Args: the arguments to be passed to the program at execution time
-  - RefreshRate: number of tenths of second between re-runs of the
-    command. A zero or negative rate means that the command will be
-    executed only once.
-  - Alias: a name to be used in the template. If the alias is en empty
-    string the program name can be used in the template.
-
-  E.g.:
-
-  #+begin_src haskell
-    Run Com "uname" ["-s","-r"] "" 0
-  #+end_src
-
-  can be used in the output template as =%uname%= (and xmobar will call
-  /uname/ only once), while
-
-  #+begin_src haskell
-    Run Com "date" ["+\"%a %b %_d %H:%M\""] "mydate" 600
-  #+end_src
-
-  can be used in the output template as =%mydate%=.
-
-  Sometimes, you don't mind if the command executed exits with an
-  error, or you might want to display a custom message in that
-  case. To that end, you can use the =ComX= variant:
-
-    =ComX ProgramName Args ExitMessage Alias RefreshRate=
-
-  Works like =Com=, but displaying =ExitMessage= (a string) if the
-  execution fails. For instance:
-
-  #+begin_src haskell
-    Run ComX "date" ["+\"%a %b %_d %H:%M\""] "N/A" "mydate" 600
-  #+end_src
-
-  will display "N/A" if for some reason the =date= invocation fails.
-
-* The DBus Interface
-
-  When compiled with the optional =with_dbus= flag, xmobar can be
-  controlled over dbus. All signals defined in [[https://github.com/jaor/xmobar/blob/master/src/Xmobar/System/Signal.hs][src/Signal.hs]] as =data
-  SignalType= can now be sent over dbus to xmobar. Due to current
-  limitations of the implementation only one process of xmobar can
-  acquire the dbus. This is handled on a first-come-first-served
-  basis, meaning that the first process will get the dbus
-  interface. Other processes will run without further problems, yet
-  have no dbus interface.
-
-  - Bus Name: =org.Xmobar.Control=
-  - Object Path: =/org/Xmobar/Control=
-  - Member Name: Any of SignalType, e.g. =string:Reveal=
-  - Interface Name: =org.Xmobar.Control=
-
-  An example using the =dbus-send= command line utility:
-
-  #+begin_src shell
-  dbus-send \
-      --session \
-      --dest=org.Xmobar.Control \
-      --type=method_call \
-      --print-reply \
-      '/org/Xmobar/Control' \
-      org.Xmobar.Control.SendSignal \
-      "string:Toggle 0"
-  #+end_src
-
-  It is also possible to send multiple signals at once:
-
-  #+begin_src shell
-    # send to another screen, reveal and toggle the persistent flag
-    dbus-send [..] \
-        "string:ChangeScreen 0" "string:Reveal 0" "string:TogglePersistent"
-  #+end_src
-
-  The =Toggle=, =Reveal=, and =Hide= signals take an additional integer
-  argument that denotes an initial delay, in tenths of a second,
-  before the command takes effect.
-
-** Example for 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
+#+title: Plugins and monitors
+
+* System Monitor Plugins
+
+  This is the description of the system monitor plugins available in
+  xmobar. Some of them are only installed when an optional build
+  option is set: we mention that fact, when needed, in their
+  description.
+
+  Each monitor has an =alias= to be used in the output
+  template. Monitors may have default aliases, see the documentation
+  of the monitor in question.
+
+  There are two types of arguments: ones that all monitors share (the
+  so called /default monitor arguments/) and arguments that are specific
+  to a certain monitor.
+
+  All Monitors accept a common set of arguments, described below in
+  [[Default Monitor Arguments]]. Some monitors also accept additional
+  options that are specific to them. When specifying the list of
+  arguments in your configuration, the common options come first,
+  followed by =--=, followed by any monitor-specific options. For
+  example, the following [[=Battery Args RefreshRate=][Battery]] configuration first sets the global
+  =template= and =Low= arguments and then specifies the battery-specific
+  =off= option.
+
+  #+begin_src haskell
+    Run Battery
+      [ "--template", "<acstatus>"
+      , "--Low"     , "15"
+      -- battery specific options start here.
+      , "--"
+      , "--off"     , "<left> (<timeleft>)"
+      ]
+      100
+  #+end_src
+
+  See also [[./window-managers.org][Interfacing with window managers]] for a collection of
+  plugins that let you interact and control xmobar from window
+  managers.
+
+*** Icon Patterns
+
+    Some monitors allow usage of strings that depend on some integer
+    value from 0 to 8 by replacing all occurrences of =%%= with it
+    (i.e. =<icon=/path/to/icon_%%.xpm/>= will be interpreted as
+    =<icon=/path/to/icon_3.xpm/>= when the value is =3=, also =%= is
+    interpreted as =%=, =%%= as =3=, =%%%= as =3%=, =%%%%= as =33= and so
+    on). Essentially it allows to replace vertical bars with custom
+    icons. For example,
+
+    #+begin_src haskell
+      Run Brightness
+        [ "-t", "<ipat>"
+        , "--"
+        , "--brightness-icon-pattern", "<icon=bright_%%.xpm/>"
+        ] 30
+    #+end_src
+
+    Will display =bright_0.xpm= to =bright_8.xpm= depending on current
+    brightness value.
+
+*** Default Monitor Arguments
+
+    These are the options available for all monitors:
+
+    - =-t= /string/ Output template
+
+      - Template for the monitor output. Field names must be enclosed
+        between pointy brackets (=<foo>=) and will be substituted by the
+        computed values. You can also specify the foreground (and
+        optionally, background) color for a region by bracketing it between
+        =<fc=fgcolor>= (or =<fc=fgcolor,bgcolor>=) and =</fc>=. The rest of
+        the template is output verbatim.
+      - Long option: =--template=
+      - Default value: per monitor (see above).
+
+    - =-H= /number/ The high threshold.
+
+      - Numerical values higher than /number/ will be displayed with the
+        color specified by =-h= (see below).
+      - Long option: =--High=
+      - Default value: 66
+
+    - =-L= /number/ The low threshold.
+
+      - Numerical values higher than /number/ and lower than the high
+        threshold will be displayed with the color specified by =-n= (see
+        below). Values lower than /number/ will use the =-l= color.
+      - Long option: =--Low=
+      - Default value: 33
+
+    - =-h= /color/ High threshold color.
+
+      - Color for displaying values above the high threshold. /color/ can be
+        either a name (e.g. "blue") or an hexadecimal RGB (e.g. "#FF0000").
+      - Long option: =--high=
+      - Default: none (use the default foreground).
+
+    - =-n= /color/ Color for 'normal' values
+
+      - Color used for values greater than the low threshold but lower than
+        the high one.
+      - Long option: =--normal=
+      - Default: none (use the default foreground).
+
+    - =-l= /color/ The low threshold color
+
+      - Color for displaying values below the low threshold.
+      - Long option: =--low=
+      - Default: none (use the default foreground).
+
+    - =-S= /boolean/ Display optional suffixes
+
+      - When set to a true designator ("True", "Yes" or "On"), optional
+        value suffixes such as the '%' symbol or optional units will be
+        displayed.
+      - Long option: =--suffix=
+      - Default: False.
+
+    - =-p= /number/ Percentages padding
+
+      - Width, in number of digits, for quantities representing percentages.
+        For instance =-p 3= means that all percentages in the monitor will
+        be represented using 3 digits.
+      - Long option: =--ppad=
+      - Default value: 0 (don't pad)
+
+    - =-d= /number/ Decimal digits
+
+      - Number of digits after the decimal period to use in float values.
+      - Long option: =--ddigits=
+      - Default value: 0 (display only integer part)
+
+    - =-m= /number/ Minimum field width
+
+      - Minimum width, in number of characters, of the fields in the monitor
+        template. Values whose printed representation is shorter than this
+        value will be padded using the padding characters given by the =-c=
+        option with the alignment specified by =-a= (see below).
+      - Long option: =--minwidth=
+      - Default: 0
+
+    - =-M= /number/ Maximum field width
+
+      - Maximum width, in number of characters, of the fields in the monitor
+        template. Values whose printed representation is longer than this
+        value will be truncated.
+      - Long option: =--maxwidth=
+      - Default: 0 (no maximum width)
+
+    - =-e= /string/ Maximum width ellipsis
+
+      - Ellipsis to be added to the field when it has reached its max width.
+      - Long option: =--maxwidthellipsis=
+      - Default: "" (no ellipsis)
+
+    - =-w= /number/ Fixed field width
+
+      - All fields will be set to this width, padding or truncating as
+        needed.
+      - Long option: =--width=
+      - Default: 0 (variable width)
+
+    - =-T= /number/ Maximum total width
+
+      - Maximum total width of the text.
+      - Long option: =--maxtwidth=
+      - Default: 0 (no limit)
+
+    - =-E= /string/ Maximum total width ellipsis
+
+      - Ellipsis to be added to the total text when it has reached its max
+        width.
+      - Long option: =--maxtwidthellipsis=
+      - Default: "" (no ellipsis)
+
+    - =-c= /string/
+
+      - Characters used for padding. The characters of /string/ are used
+        cyclically. E.g., with =-P +- -w 6=, a field with value "foo" will
+        be represented as "+-+foo".
+      - Long option: =--padchars=
+      - Default value: " "
+
+    - =-a= r|l Field alignment
+
+      - Whether to use right (r) or left (l) alignment of field values when
+        padding.
+      - Long option: =--align=
+      - Default value: r (padding to the left)
+
+    - =-b= /string/ Bar background
+
+      - Characters used, cyclically, to draw the background of bars. For
+        instance, if you set this option to "·.", an empty bar will look
+        like this: =·.·.·.·.·.=
+      - Long option: =--bback=
+      - Default value: ":"
+
+    - =-f= /string/ Bar foreground
+
+      - Characters used, cyclically, to draw the foreground of bars.
+      - Long option: =--bfore=
+      - Default value: "#"
+
+    - =-W= /number/ Bar width
+
+      - Total number of characters used to draw bars.
+      - Long option: =--bwidth=
+      - Default value: 10
+      - Special value: 0. When this parameter is 0, the percentage to
+        display is interpreted as a position in the bar foreground string
+        (given by =-f=), and the character at that position is displayed.
+
+    - =-x= /string/ N/A string
+
+      - String to be used when the monitor is not available
+      - Long option: =--nastring=
+      - Default value: "N/A"
+
+    Commands' arguments must be set as a list. E.g.:
+
+    #+begin_src haskell
+      Run Weather "EGPF" ["-t", "<station>: <tempC>C"] 36000
+    #+end_src
+
+    In this case xmobar will run the weather monitor, getting information
+    for the weather station ID EGPF (Glasgow Airport, as a homage to GHC)
+    every hour (36000 tenth of seconds), with a template that will output
+    something like:
+
+    #+begin_src shell
+      Glasgow Airport: 16.0C
+    #+end_src
+
+*** Battery Monitors
+***** =Battery Args RefreshRate=
+
+      Same as
+
+      #+begin_src haskell
+        BatteryP ["BAT", "BAT0", "BAT1", "BAT2"] Args RefreshRate
+      #+end_src
+
+***** =BatteryP Dirs Args RefreshRate=
+
+      - Aliases to =battery=
+
+      - Dirs: list of directories in =/sys/class/power_supply/= where to look
+        for the ACPI files of each battery. Example: =["BAT0","BAT1","BAT2"]=.
+        Only up to 3 existing directories will be searched.
+
+      - Args: default monitor arguments, plus the following specific ones
+        (these options, being specific to the monitor, are to be specified
+        after a =--= in the argument list):
+
+        - =-O=: string for AC "on" status (default: "On")
+        - =-i=: string for AC "idle" status (default: "On")
+        - =-o=: string for AC "off" status (default: "Off")
+        - =-L=: low power (=watts=) threshold (default: 10)
+        - =-H=: high power threshold (default: 12)
+        - =-l=: color to display power lower than the =-L= threshold
+        - =-m=: color to display power lower than the =-H= threshold
+        - =-h=: color to display power higher than the =-H= threshold
+        - =-p=: color to display positive power (battery charging)
+        - =-f=: file in =/sys/class/power_supply= with AC info (default:
+          "AC/online")
+        - =-A=: a number between 0 and 100, threshold below which the action
+          given by =-a=, if any, is performed (default: 5)
+        - =-a=: a string with a system command that is run when the percentage
+          left in the battery is less or equal than the threshold given by the
+          =-A= option. If not present, no action is undertaken.
+        - =-P=: to include a percentage symbol in =left=.
+        - =--on-icon-pattern=: dynamic string for current battery charge when
+          AC is "on" in =leftipat=.
+        - =--off-icon-pattern=: dynamic string for current battery charge when
+          AC is "off" in =leftipat=.
+        - =--idle-icon-pattern=: dynamic string for current battery charge
+          when AC is "idle" in =leftipat=.
+        - =--lows=: string for AC "off" status and power lower than the =-L=
+          threshold (default: "")
+        - =--mediums=: string for AC "off" status and power lower than the
+          =-H= threshold (default: "")
+        - =--highs=: string for AC "off" status and power higher than the =-H=
+          threshold (default: "")
+
+      - Variables that can be used with the =-t/--template= argument:
+        =left=, =leftbar=, =leftvbar=, =leftipat=, =timeleft=, =watts=,
+        =acstatus=
+
+      - Default template: =Batt: <watts>, <left>% / <timeleft>=
+
+      - Example (note that you need "--" to separate regular monitor options
+        from Battery's specific ones):
+
+        #+begin_src haskell
+          Run BatteryP ["BAT0"]
+                       ["-t", "<acstatus><watts> (<left>%)",
+                        "-L", "10", "-H", "80", "-p", "3",
+                        "--", "-O", "<fc=green>On</fc> - ", "-i", "",
+                        "-L", "-15", "-H", "-5",
+                        "-l", "red", "-m", "blue", "-h", "green",
+                        "-a", "notify-send -u critical 'Battery running out!!'",
+                        "-A", "3"]
+                       600
+        #+end_src
+
+        In the above example, the thresholds before the =--= separator affect
+        only the =<left>= and =<leftbar>= fields, while those after the
+        separator affect how =<watts>= is displayed. For this monitor, neither
+        the generic nor the specific options have any effect on =<timeleft>=.
+        We are also telling the monitor to execute the unix command
+        =notify-send= when the percentage left in the battery reaches 6%.
+
+        It is also possible to specify template variables in the =-O= and =-o=
+        switches, as in the following example:
+
+        #+begin_src haskell
+          Run BatteryP ["BAT0"]
+                       ["-t", "<acstatus>"
+                       , "-L", "10", "-H", "80"
+                       , "-l", "red", "-h", "green"
+                       , "--", "-O", "Charging", "-o", "Battery: <left>%"
+                       ] 10
+        #+end_src
+
+      - The "idle" AC state is selected whenever the AC power entering the
+        battery is zero.
+
+***** =BatteryN Dirs Args RefreshRate Alias=
+
+      Works like =BatteryP=, but lets you specify an alias for the
+      monitor other than "battery". Useful in case you one separate
+      monitors for more than one battery.
+*** Cpu and Memory Monitors
+***** =Cpu Args RefreshRate=
+
+      - Aliases to =cpu=
+      - Args: default monitor arguments, plus:
+
+        - =--load-icon-pattern=: dynamic string for cpu load in =ipat=
+
+      - Thresholds refer to percentage of CPU load
+      - Variables that can be used with the =-t/--template= argument:
+        =total=, =bar=, =vbar=, =ipat=, =user=, =nice=, =system=, =idle=,
+        =iowait=
+      - Default template: =Cpu: <total>%=
+
+***** =MultiCpu Args RefreshRate=
+
+      - Aliases to =multicpu=
+      - Args: default monitor arguments, plus:
+
+        - =--load-icon-pattern=: dynamic string for overall cpu load in
+          =ipat=.
+        - =--load-icon-patterns=: dynamic string for each cpu load in
+          =autoipat=, =ipat{i}=. This option can be specified several times.
+          nth option corresponds to nth cpu.
+        - =--fallback-icon-pattern=: dynamic string used by =autoipat= and
+          =ipat{i}= when no =--load-icon-patterns= has been provided for
+          =cpu{i}=
+        - =--contiguous-icons=: flag (no value needs to be provided) that
+          causes the load icons to be drawn without padding.
+
+      - Thresholds refer to percentage of CPU load
+      - Variables that can be used with the =-t/--template= argument:
+        =autototal=, =autobar=, =autovbar=, =autoipat=, =autouser=,
+        =autonice=, =autosystem=, =autoidle=, =total=, =bar=, =vbar=, =ipat=,
+        =user=, =nice=, =system=, =idle=, =total0=, =bar0=, =vbar0=, =ipat0=,
+        =user0=, =nice0=, =system0=, =idle0=, ... The auto* variables
+        automatically detect the number of CPUs on the system and display one
+        entry for each.
+      - Default template: =Cpu: <total>%=
+
+***** =CpuFreq Args RefreshRate=
+
+      - Aliases to =cpufreq=
+
+      - Args: default monitor arguments
+
+      - Thresholds refer to frequency in GHz
+
+      - Variables that can be used with the =-t/--template= argument:
+        =cpu0=, =cpu1=, .., =cpuN=
+
+      - Default template: =Freq: <cpu0>GHz=
+
+      - This monitor requires acpi_cpufreq module to be loaded in kernel
+
+      - Example:
+
+        #+begin_src haskell
+          Run CpuFreq ["-t", "Freq:<cpu0>|<cpu1>GHz", "-L", "0", "-H", "2",
+                       "-l", "lightblue", "-n","white", "-h", "red"] 50
+        #+end_src
+
+***** =CoreTemp Args RefreshRate=
+
+      - Aliases to =coretemp=
+
+      - Args: default monitor arguments
+
+      - Thresholds refer to temperature in degrees
+
+      - Variables that can be used with the =-t/--template= argument:
+        =core0=, =core1=, .., =coreN=
+
+      - Default template: =Temp: <core0>C=
+
+      - This monitor requires coretemp module to be loaded in kernel
+
+      - Example:
+
+        #+begin_src haskell
+          Run CoreTemp ["-t", "Temp:<core0>|<core1>C",
+                        "-L", "40", "-H", "60",
+                        "-l", "lightblue", "-n", "gray90", "-h", "red"] 50
+        #+end_src
+
+***** =MultiCoreTemp Args RefreshRate=
+
+      - Aliases to =multicoretemp=
+
+      - Args: default monitor arguments, plus:
+
+        - =--max-icon-pattern=: dynamic string for overall cpu load in
+          =maxipat=.
+        - =--avg-icon-pattern=: dynamic string for overall cpu load in
+          =avgipat=.
+        - =--mintemp=: temperature in degree Celsius, that sets the lower
+          limit for percentage calculation.
+        - =--maxtemp=: temperature in degree Celsius, that sets the upper
+          limit for percentage calculation.
+        - =--hwmon-path=: this monitor tries to find coretemp devices by
+          looking for them in directories following the pattern
+          =/sys/bus/platform/devices/coretemp.*/hwmon/hwmon*=, but some
+          processors (notably Ryzen) might expose those files in a different
+          tree (e.g., Ryzen) puts them somewhere in "/sys/class/hwmon/hwmon*",
+          and the lookup is most costly. With this option, it is possible to
+          explicitly specify the full path to the directory where the
+          =tempN_label= and =tempN_input= files are located.
+
+      - Thresholds refer to temperature in degree Celsius
+
+      - Variables that can be used with the =-t/--template= argument: =max=,
+        =maxpc=, =maxbar=, =maxvbar=, =maxipat=, =avg=, =avgpc=, =avgbar=,
+        =avgvbar=, =avgipat=, =core0=, =core1=, ..., =coreN=
+
+        The /pc, /bar, /vbar and /ipat variables are showing percentages on
+        the scale defined by =--mintemp= and =--maxtemp=. The max* and avg*
+        variables to the highest and the average core temperature.
+
+      - Default template: =Temp: <max>°C - <maxpc>%=
+
+      - This monitor requires coretemp module to be loaded in kernel
+
+      - Example:
+
+        #+begin_src haskell
+          Run MultiCoreTemp ["-t", "Temp: <avg>°C | <avgpc>%",
+                             "-L", "60", "-H", "80",
+                             "-l", "green", "-n", "yellow", "-h", "red",
+                             "--", "--mintemp", "20", "--maxtemp", "100"] 50
+        #+end_src
+
+***** =K10Temp Slot Args RefreshRate=
+
+      - Aliases to =k10temp=
+
+      - Slot: The PCI slot address of the k10temp device as a string.  You
+        can find it as a subdirectory in =/sys/bus/pci/drivers/k10temp/=.
+
+      - Args: default monitor arguments
+
+      - Thresholds refer to temperature in degrees
+
+      - Variables that can be used with the =-t/--template= argument:
+        =Tctl=, =Tdie=, =Tccd1=, .., =Tccd8=
+
+      - Default template: =Temp: <Tdie>C=
+
+      - This monitor requires k10temp module to be loaded in kernel
+
+      - It is important to note that not all measurements are available
+        on on all models of processor. Of particular importance - Tdie
+        (used in the default template) may not be present on processors
+        prior to Zen (17h). Tctl, however, may be offset from the real
+        temperature and so is not used by default.
+
+      - Example:
+
+        #+begin_src haskell
+          Run K10Temp "0000:00:18.3"
+                      ["-t", "Temp: <Tdie>C|<Tccd1>C",
+                       "-L", "40", "-H", "60",
+                       "-l", "lightblue", "-n", "gray90", "-h", "red"]
+                      50
+        #+end_src
+
+
+***** =Memory Args RefreshRate=
+
+      - Aliases to =memory=
+      - Args: default monitor arguments, plus:
+
+        - =--used-icon-pattern=: dynamic string for used memory ratio in
+          =usedipat=.
+        - =--free-icon-pattern=: dynamic string for free memory ratio in
+          =freeipat=.
+        - =--available-icon-pattern=: dynamic string for available memory
+          ratio in =availableipat=.
+
+      - Thresholds refer to percentage of used memory
+      - Variables that can be used with the =-t/--template= argument:
+        =total=, =free=, =buffer=, =cache=, =available=, =used=, =usedratio=,
+        =usedbar=, =usedvbar=, =usedipat=, =freeratio=, =freebar=, =freevbar=,
+        =freeipat=, =availableratio=, =availablebar=, =availablevbar=,
+        =availableipat=
+      - Default template: =Mem: <usedratio>% (<cache>M)=
+
+***** =Swap Args RefreshRate=
+
+      - Aliases to =swap=
+      - Args: default monitor arguments
+      - Thresholds refer to percentage of used swap
+      - Variables that can be used with the =-t/--template= argument:
+        =total=, =used=, =free=, =usedratio=
+      - Default template: =Swap: <usedratio>%=
+
+*** Date Monitors
+***** =Date Format Alias RefreshRate=
+
+      - Format is a time format string, as accepted by the standard ISO C
+        =strftime= function (or Haskell's =formatCalendarTime=).  Basically,
+        if =date +"my-string"= works with your command then =Date= will handle
+        it correctly.
+
+      - Timezone changes are picked up automatically every minute.
+
+      - Sample usage:
+
+        #+begin_src haskell
+          Run Date "%a %b %_d %Y <fc=#ee9a00>%H:%M:%S</fc>" "date" 10
+        #+end_src
+
+***** =DateZone Format Locale Zone Alias RefreshRate=
+
+      A variant of the =Date= monitor where one is able to explicitly set the
+      time-zone, as well as the locale.
+
+      - The format of =DateZone= is exactly the same as =Date=.
+
+      - If =Locale= is =""= (the empty string) the default locale of the
+        system is used, otherwise use the given locale. If there are more
+        instances of =DateZone=, using the empty string as input for =Locale=
+        is not recommended.
+
+      - =Zone= is the name of the =TimeZone=. It is assumed that the time-zone
+        database is stored in =/usr/share/zoneinfo/=. If the empty string is
+        given as =Zone=, the default system time is used.
+
+      - Sample usage:
+
+        #+begin_src haskell
+          Run DateZone "%a %H:%M:%S" "de_DE.UTF-8" "Europe/Vienna" "viennaTime" 10
+        #+end_src
+*** Disk Monitors
+***** =DiskU Disks Args RefreshRate=
+
+      - Aliases to =disku=
+
+      - Disks: list of pairs of the form (device or mount point, template),
+        where the template can contain =<size>=, =<free>=, =<used>=, =<freep>=
+        or =<usedp>=, =<freebar>=, =<freevbar>=, =<freeipat>=, =<usedbar>=,
+        =<usedvbar>= or =<usedipat>= for total, free, used, free percentage
+        and used percentage of the given file system capacity.
+
+      - Thresholds refer to usage percentage.
+
+      - Args: default monitor arguments. =-t/--template= is ignored. Plus
+
+        - =--free-icon-pattern=: dynamic string for free disk space in
+          =freeipat=.
+        - =--used-icon-pattern=: dynamic string for used disk space in
+          =usedipat=.
+
+      - Default template: none (you must specify a template for each file
+        system).
+
+      - Example:
+
+        #+begin_src haskell
+          DiskU [("/", "<used>/<size>"), ("sdb1", "<usedbar>")]
+                ["-L", "20", "-H", "50", "-m", "1", "-p", "3"]
+                20
+        #+end_src
+
+***** =DiskIO Disks Args RefreshRate=
+
+      - Aliases to =diskio=
+
+      - Disks: list of pairs of the form (device or mount point, template),
+        where the template can contain =<total>=, =<read>=, =<write>= for
+        total, read and write speed, respectively, as well as =<totalb>=,
+        =<readb>=, =<writeb>=, which report number of bytes during the last
+        refresh period rather than speed. There are also bar versions of each:
+        =<totalbar>=, =<totalvbar>=, =<totalipat>=, =<readbar>=, =<readvbar>=,
+        =<readipat>=, =<writebar>=, =<writevbar>=, and =<writeipat>=; and
+        their "bytes" counterparts: =<totalbbar>=, =<totalbvbar>=,
+        =<totalbipat>=, =<readbbar>=, =<readbvbar>=, =<readbipat>=,
+        =<writebbar>=, =<writebvbar>=, and =<writebipat>=.
+
+      - Thresholds refer to speed in b/s
+
+      - Args: default monitor arguments. =-t/--template= is ignored. Plus
+
+        - =--total-icon-pattern=: dynamic string for total disk I/O in
+          =<totalipat>=.
+        - =--write-icon-pattern=: dynamic string for write disk I/O in
+          =<writeipat>=.
+        - =--read-icon-pattern=: dynamic string for read disk I/O in
+          =<readipat>=.
+
+      - Default template: none (you must specify a template for each file
+        system).
+
+      - Example:
+
+        #+begin_src haskell
+          DiskIO [("/", "<read> <write>"), ("sdb1", "<total>")] [] 10
+        #+end_src
+
+*** Keyboard Monitors
+***** =Kbd Opts=
+
+      - Registers to XKB/X11-Events and output the currently active keyboard
+        layout. Supports replacement of layout names.
+
+      - Aliases to =kbd=
+
+      - Opts is a list of tuples:
+
+        - first element of the tuple is the search string
+        - second element of the tuple is the corresponding replacement
+
+      - Example:
+
+        #+begin_src haskell
+          Run Kbd [("us(dvorak)", "DV"), ("us", "US")]
+        #+end_src
+
+***** =Locks=
+
+      - Displays the status of Caps Lock, Num Lock and Scroll Lock.
+
+      - Aliases to =locks=
+
+      - Example:
+
+        #+begin_src haskell
+          Run Locks
+        #+end_src
+
+*** Process Monitors
+***** =TopProc Args RefreshRate=
+
+      - Aliases to =top=
+      - Args: default monitor arguments. The low and high thresholds (=-L= and
+        =-H=) denote, for memory entries, the percent of the process memory
+        over the total amount of memory currently in use and, for cpu entries,
+        the activity percentage (i.e., the value of =cpuN=, which takes values
+        between 0 and 100).
+      - Variables that can be used with the =-t/--template= argument: =no=,
+        =name1=, =cpu1=, =both1=, =mname1=, =mem1=, =mboth1=, =name2=, =cpu2=,
+        =both2=, =mname2=, =mem2=, =mboth2=, ...
+      - Default template: =<both1>=
+      - Displays the name and cpu/mem usage of running processes (=bothn= and
+        =mboth= display both, and is useful to specify an overall maximum
+        and/or minimum width, using the =-m/-M= arguments. =no= gives the
+        total number of processes.
+
+***** =TopMem Args RefreshRate=
+
+      - Aliases to =topmem=
+      - Args: default monitor arguments. The low and high thresholds (=-L= and
+        =-H=) denote the percent of the process memory over the total amount
+        of memory currently in use.
+      - Variables that can be used with the =-t/--template= argument:
+        =name1=, =mem1=, =both1=, =name2=, =mem2=, =both2=, ...
+      - Default template: =<both1>=
+      - Displays the name and RSS (resident memory size) of running processes
+        (=bothn= displays both, and is useful to specify an overall maximum
+        and/or minimum width, using the =-m/-M= arguments.
+
+*** Thermal Monitors
+***** =ThermalZone Number Args RefreshRate=
+
+      - Aliases to "thermaln": so =ThermalZone 0 []= can be used in template
+        as =%thermal0%=
+
+      - Thresholds refer to temperature in degrees
+
+      - Args: default monitor arguments
+
+      - Variables that can be used with the =-t/--template= argument: =temp=
+
+      - Default template: =<temp>C=
+
+      - This plugin works only on systems with devices having thermal zone.
+        Check directories in =/sys/class/thermal= for possible values of the
+        zone number (e.g., 0 corresponds to =thermal_zone0= in that
+        directory).
+
+      - Example:
+
+        #+begin_src haskell
+          Run ThermalZone 0 ["-t","<id>: <temp>C"] 30
+        #+end_src
+
+***** =Thermal Zone Args RefreshRate=
+
+      - *This plugin is deprecated. Use =ThermalZone= instead.*
+
+      - Aliases to the Zone: so =Thermal "THRM" []= can be used in template as
+        =%THRM%=
+
+      - Args: default monitor arguments
+
+      - Thresholds refer to temperature in degrees
+
+      - Variables that can be used with the =-t/--template= argument: =temp=
+
+      - Default template: =Thm: <temp>C=
+
+      - This plugin works only on systems with devices having thermal zone.
+        Check directories in /proc/acpi/thermal_zone for possible values.
+
+      - Example:
+
+        #+begin_src haskell
+          Run Thermal "THRM" ["-t","iwl4965-temp: <temp>C"] 50
+        #+end_src
+
+*** Volume Monitors
+***** =Volume Mixer Element Args RefreshRate=
+
+      - Aliases to the mixer name and element name separated by a
+        colon. Thus, =Volume "default" "Master" [] 10= can be used as
+        =%default:Master%=.
+      - Args: default monitor arguments. Also accepts:
+
+        - =-O= /string/ On string
+
+          - The string used in place of =<status>= when the mixer element is
+            on. Defaults to "[on]".
+          - Long option: =--on=
+
+        - =-o= /string/ Off string
+
+          - The string used in place of =<status>= when the mixer element is
+            off. Defaults to "[off]".
+          - Long option: =--off=
+
+        - =-C= /color/ On color
+
+          - The color to be used for =<status>= when the mixer element is on.
+            Defaults to "green".
+          - Long option: =--onc=
+
+        - =-c= /color/ Off color
+
+          - The color to be used for =<status>= when the mixer element is off.
+            Defaults to "red".
+          - Long option: =--offc=
+
+        - =--highd= /number/ High threshold for dB. Defaults to -5.0.
+        - =--lowd= /number/ Low threshold for dB. Defaults to -30.0.
+        - =--volume-icon-pattern= /string/ dynamic string for current volume
+          in =volumeipat=.
+        - =-H= /number/ High threshold for volume (in %). Defaults to 60.0.
+
+          - Long option: =--highv=
+
+        - =-L= /number/ Low threshold for volume (in %). Defaults to 20.0.
+
+          - Long option: =--lowv=
+
+        - =-h=: /string/ High string
+
+          - The string added in front of =<status>= when the mixer element is
+            on and the volume percentage is higher than the =-H= threshold.
+            Defaults to "".
+          - Long option: =--highs=
+
+        - =-m=: /string/ Medium string
+
+          - The string added in front of =<status>= when the mixer element is
+            on and the volume percentage is lower than the =-H= threshold.
+            Defaults to "".
+          - Long option: =--mediums=
+
+        - =-l=: /string/ Low string
+
+          - The string added in front of =<status>= when the mixer element is
+            on and the volume percentage is lower than the =-L= threshold.
+            Defaults to "".
+          - Long option: =--lows=
+
+      - Variables that can be used with the =-t/--template= argument:
+        =volume=, =volumebar=, =volumevbar=, =volumeipat=, =dB=, =status=,
+        =volumestatus=
+      - Note that =dB= might only return 0 on your system. This is known to
+        happen on systems with a pulseaudio backend.
+      - Default template: =Vol: <volume>% <status>=
+      - Requires the package [[http://hackage.haskell.org/package/alsa-core][alsa-core]] and [[http://hackage.haskell.org/package/alsa-mixer][alsa-mixer]] installed in your
+        system. In addition, to activate this plugin you must pass the
+        =with_alsa= flag during compilation.
+
+***** =Alsa Mixer Element Args=
+
+      Like [[=Volume Mixer Element Args RefreshRate=][Volume]] but with the following differences:
+
+      - Uses event-based refreshing via =alsactl monitor= instead of polling,
+        so it will refresh instantly when there's a volume change, and won't
+        use CPU until a change happens.
+      - Aliases to =alsa:= followed by the mixer name and element name
+        separated by a colon. Thus, =Alsa "default" "Master" []= can be used
+        as =%alsa:default:Master%=.
+      - Additional options (after the =--=):
+        - =--alsactl=/path/to/alsactl=: If this option is not specified,
+          =alsactl= will be sought in your =PATH= first, and failing that, at
+          =/usr/sbin/alsactl= (this is its location on Debian systems.
+          =alsactl monitor= works as a non-root user despite living in
+          =/usr/sbin=.).
+        - =stdbuf= (from coreutils) must be (and most probably already is) in
+          your =PATH=.
+
+*** Mail Monitors
+***** =Mail Args Alias=
+
+      - Args: list of maildirs in form =[("name1","path1"),...]=. Paths may
+        start with a '~' to expand to the user's home directory.
+
+      - This plugin requires inotify support in your Linux kernel and the
+        [[http://hackage.haskell.org/package/hinotify/][hinotify]] package. To activate, pass the =with_inotify= flag during
+        compilation.
+
+      - Example:
+
+        #+begin_src haskell
+          Run Mail [("inbox", "~/var/mail/inbox"),
+                    ("lists", "~/var/mail/lists")]
+                   "mail"
+        #+end_src
+
+***** =MailX Args Opts Alias=
+
+      - Args: list of maildirs in form =[("name1","path1","color1"),...]=.
+        Paths may start with a '~' to expand to the user's home directory.
+        When mails are present, counts are displayed with the given name and
+        color.
+
+      - Opts is a possibly empty list of options, as flags. Possible values:
+        -d dir --dir dir a string giving the base directory where maildir
+        files with a relative path live. -p prefix --prefix prefix a string
+        giving a prefix for the list of displayed mail counts -s suffix
+        --suffix suffix a string giving a suffix for the list of displayed
+        mail counts
+
+      - This plugin requires inotify support in your Linux kernel and the
+        [[http://hackage.haskell.org/package/hinotify/][hinotify]] package. To activate, pass the =with_inotify= flag during
+        compilation.
+
+      - Example:
+
+        #+begin_src haskell
+          Run MailX [("I", "inbox", "green"),
+                     ("L", "lists", "orange")]
+                    ["-d", "~/var/mail", "-p", " ", "-s", " "]
+                    "mail"
+        #+end_src
+
+***** =MBox Mboxes Opts Alias=
+
+      - Mboxes a list of mbox files of the form =[("name", "path", "color")]=,
+        where name is the displayed name, path the absolute or relative (to
+        BaseDir) path of the mbox file, and color the color to use to display
+        the mail count (use an empty string for the default).
+
+      - Opts is a possibly empty list of options, as flags. Possible values:
+        -a --all (no arg) Show all mailboxes, even if empty. -u (no arg) Show
+        only the mailboxes' names, sans counts. -d dir --dir dir a string
+        giving the base directory where mbox files with a relative path live.
+        -p prefix --prefix prefix a string giving a prefix for the list of
+        displayed mail counts -s suffix --suffix suffix a string giving a
+        suffix for the list of displayed mail counts
+
+      - Paths may start with a '~' to expand to the user's home directory.
+
+      - This plugin requires inotify support in your Linux kernel and the
+        [[http://hackage.haskell.org/package/hinotify/][hinotify]] package. To activate, pass the =with_inotify= flag during
+        compilation.
+
+      - Example. The following command look for mails in =/var/mail/inbox= and
+        =~/foo/mbox=, and will put a space in front of the printed string
+        (when it's not empty); it can be used in the template with the alias
+        =mbox=:
+
+        #+begin_src haskell
+          Run MBox [("I ", "inbox", "red"), ("O ", "~/foo/mbox", "")]
+                   ["-d", "/var/mail/", "-p", " "] "mbox"
+        #+end_src
+
+***** =NotmuchMail Alias Args Rate=
+
+      This plugin checks for new mail, provided that this mail is indexed by
+      =notmuch=. In the =notmuch= spirit, this plugin checks for new *threads*
+      and not new individual messages.
+
+      - Alias: What name the plugin should have in your template string.
+
+      - Args: A list of =MailItem= s of the form
+
+        #+begin_src haskell
+          [ MailItem "name" "address" "query"
+          ...
+          ]
+        #+end_src
+
+        where
+
+        - =name= is what gets printed in the status bar before the number of
+          new threads.
+        - =address= is the e-mail address of the recipient, i.e. we only query
+          mail that was send to this particular address (in more concrete
+          terms, we pass the address to the =to:= constructor when performing
+          the search). If =address= is empty, we search through all unread
+          mail, regardless of whom it was sent to.
+        - =query= is funneled to =notmuch search= verbatim. For the general
+          query syntax, consult =notmuch search --help=, as well as
+          =notmuch-search-terms(7)=. Note that the =unread= tag is *always*
+          added in front of the query and composed with it via an *and*.
+
+      - Rate: Rate with which to update the plugin (in deciseconds).
+
+      - Example:
+
+        - A single =MailItem= that displays all unread threads from the given
+          address:
+
+          #+begin_src haskell
+            MailItem "mbs:" "soliditsallgood@mailbox.org" ""
+          #+end_src
+
+        - A single =MailItem= that displays all unread threads with
+          "[My-Subject]" somewhere in the title:
+
+          #+begin_src haskell
+            MailItem "S:" "" "subject:[My-Subject]"
+          #+end_src
+
+        - A full example of a =NotmuchMail= configuration:
+
+          #+begin_src haskell
+            Run NotmuchMail "mail"  -- name for the template string
+              [ -- All unread mail to the below address, but nothing that's tagged
+                -- with @lists@ or @haskell@.
+                MailItem "mbs:"
+                         "soliditsallgood@mailbox.org"
+                         "not tag:lists and not tag:haskell"
+
+                -- All unread mail that has @[Haskell-Cafe]@ in the subject line.
+              , MailItem "C:" "" "subject:[Haskell-Cafe]"
+
+                -- All unread mail that's tagged as @lists@, but not @haskell@.
+              , MailItem "H:" "" "tag:lists and not tag:haskell"
+              ]
+              600                   -- update every 60 seconds
+          #+end_src
+
+*** Music Monitors
+***** =MPD Args RefreshRate=
+
+      - This monitor will only be compiled if you ask for it using the
+        =with_mpd= flag. It needs [[http://hackage.haskell.org/package/libmpd/][libmpd]] 5.0 or later (available on Hackage).
+
+      - Aliases to =mpd=
+
+      - Args: default monitor arguments. In addition you can provide =-P=,
+        =-S= and =-Z=, with an string argument, to represent the playing,
+        stopped and paused states in the =statei= template field. The
+        environment variables =MPD_HOST= and =MPD_PORT= are used to configure
+        the mpd server to communicate with, unless given in the additional
+        arguments =-p= (=--port=) and =-h= (=--host=). Also available:
+
+        - =lapsed-icon-pattern=: dynamic string for current track position in
+          =ipat=.
+
+      - Variables that can be used with the =-t/--template= argument: =bar=,
+        =vbar=, =ipat=, =state=, =statei=, =volume=, =length=, =lapsed=,
+        =remaining=, =plength= (playlist length), =ppos= (playlist position),
+        =flags= (ncmpcpp-style playback mode), =name=, =artist=, =composer=,
+        =performer=, =album=, =title=, =track=, =file=, =genre=, =date=
+
+      - Default template: =MPD: <state>=
+
+      - Example (note that you need "--" to separate regular monitor options
+        from MPD's specific ones):
+
+        #+begin_src haskell
+          Run MPD ["-t",
+                   "<composer> <title> (<album>) <track>/<plength> <statei> [<flags>]",
+                   "--", "-P", ">>", "-Z", "|", "-S", "><"] 10
+        #+end_src
+
+***** =MPDX Args RefreshRate Alias=
+
+      Like =MPD= but uses as alias its last argument instead of "mpd".
+
+***** =Mpris1 PlayerName Args RefreshRate=
+
+      - Aliases to =mpris1=
+
+      - Requires [[http://hackage.haskell.org/package/dbus][dbus]] and [[http://hackage.haskell.org/package/text][text]] packages. To activate, pass the =with_mpris=
+        flag during compilation.
+
+      - PlayerName: player supporting MPRIS v1 protocol. Some players need
+        this to be an all lowercase name (e.g. "spotify"), but some others
+        don't.
+
+      - Args: default monitor arguments.
+
+      - Variables that can be used with the =-t/--template= argument:
+        =album=, =artist=, =arturl=, =length=, =title=, =tracknumber=
+
+      - Default template: =<artist> - <title>=
+
+      - Example:
+
+        #+begin_src haskell
+          Run Mpris1 "clementine" ["-t", "<artist> - [<tracknumber>] <title>"] 10
+        #+end_src
+
+***** =Mpris2 PlayerName Args RefreshRate=
+
+      - Aliases to =mpris2=
+
+      - Requires [[http://hackage.haskell.org/package/dbus][dbus]] and [[http://hackage.haskell.org/package/text][text]] packages. To activate, pass the =with_mpris=
+        flag during compilation.
+
+      - PlayerName: player supporting MPRIS v2 protocol. Some players need
+        this to be an all lowercase name (e.g. "spotify"), but some others
+        don't.
+
+      - Args: default monitor arguments.
+
+      - Variables that can be used with the =-t/--template= argument:
+        =album=, =artist=, =arturl=, =length=, =title=, =tracknumber=,
+        =composer=, =genre=
+
+      - Default template: =<artist> - <title>=
+
+      - Example:
+
+        #+begin_src haskell
+          Run Mpris2 "spotify" ["-t", "<artist> - [<composer>] <title>"] 10
+        #+end_src
+
+*** Network Monitors
+***** =Network Interface Args RefreshRate=
+
+      - Aliases to the interface name: so =Network "eth0" []= can be used as
+        =%eth0%=
+      - Thresholds refer to velocities expressed in Kb/s
+      - Args: default monitor arguments, plus:
+
+        - =--rx-icon-pattern=: dynamic string for reception rate in =rxipat=.
+        - =--tx-icon-pattern=: dynamic string for transmission rate in
+          =txipat=.
+        - =--up=: string used for the =up= variable value when the interface
+          is up.
+
+      - Variables that can be used with the =-t=/=--template= argument: =dev=,
+        =rx=, =tx=, =rxbar=, =rxvbar=, =rxipat=, =txbar=, =txvbar=, =txipat=,
+        =up=. Reception and transmission rates (=rx= and =tx=) are displayed
+        by default as Kb/s, without any suffixes, but you can set the =-S= to
+        "True" to make them displayed with adaptive units (Kb/s, Mb/s, etc.).
+      - Default template: =<dev>: <rx>KB|<tx>KB=
+
+***** =DynNetwork Args RefreshRate=
+
+      - Active interface is detected automatically
+      - Aliases to "dynnetwork"
+      - Thresholds are expressed in Kb/s
+      - Args: default monitor arguments, plus:
+
+      - =--rx-icon-pattern=: dynamic string for reception rate in =rxipat=.
+      - =--tx-icon-pattern=: dynamic string for transmission rate in =txipat=
+      - =--devices=: comma-separated list of devices to show.
+
+      - Variables that can be used with the =-t=/=--template= argument:
+        =dev=, =rx=, =tx=, =rxbar=, =rxvbar=, =rxipat=, =txbar=, =txvbar=,
+        =txipat=.
+
+      Reception and transmission rates (=rx= and =tx=) are displayed in Kbytes
+      per second, and you can set the =-S= to "True" to make them displayed
+      with units (the string "Kb/s").
+      - Default template: =<dev>: <rx>KB|<tx>KB=
+      - Example of usage of =--devices= option:
+
+          =["--", "--devices", "wlp2s0,enp0s20f41"]=
+
+***** =Wireless Interface Args RefreshRate=
+
+      - If set to "", first suitable wireless interface is used.
+      - Aliases to the interface name with the suffix "wi": thus,
+        =Wireless   "wlan0" []= can be used as =%wlan0wi%=, and
+        =Wireless "" []= as =%wi%=.
+      - Args: default monitor arguments, plus:
+
+        - =--quality-icon-pattern=: dynamic string for connection quality in
+          =qualityipat=.
+
+      - Variables that can be used with the =-t=/=--template= argument:
+        =ssid=, =signal=, =quality=, =qualitybar=, =qualityvbar=,
+        =qualityipat=
+      - Thresholds refer to link quality on a =[0, 100]= scale. Note that
+        =quality= is calculated from =signal= (in dBm) by a possibly lossy
+        conversion. It is also not taking into account many factors such as
+        noise level, air busy time, transcievers' capabilities and the others
+        which can have drastic impact on the link performance.
+      - Default template: =<ssid> <quality>=
+      - To activate this plugin you must pass the =with_nl80211= or the
+        =with_iwlib= flag during compilation.
+
+*** Weather Monitors
+***** =Weather StationID Args RefreshRate=
+
+      - Aliases to the Station ID: so =Weather "LIPB" []= can be used in
+        template as =%LIPB%=
+      - Thresholds refer to temperature in the selected units
+      - Args: default monitor arguments, plus:
+
+        - =--weathers= /string/ : display a default string when the =weather=
+          variable is not reported.
+
+          - short option: =-w=
+          - Default: ""
+
+        - =--useManager= /bool/ : Whether to use one single manager per
+          monitor for managing network connections or create a new one every
+          time a connection is made.
+
+          - Short option: =-m=
+          - Default: True
+
+      - Variables that can be used with the =-t/--template= argument:
+        =station=, =stationState=, =year=, =month=, =day=, =hour=,
+        =windCardinal=, =windAzimuth=, =windMph=, =windKnots=, =windMs=,
+        =windKmh= =visibility=, =skyCondition=, =weather=, =tempC=, =tempF=,
+        =dewPointC=, =dewPointF=, =rh=, =pressure=
+      - Default template: =<station>: <tempC>C, rh <rh>% (<hour>)=
+      - Retrieves weather information from http://tgftp.nws.noaa.gov. Here is
+        an [[https://tgftp.nws.noaa.gov/data/observations/metar/decoded/CYLD.TXT][example]], also showcasing the kind of information that may be
+        extracted. Here is [[https://weather.rap.ucar.edu/surface/stations.txt][a sample list of station IDs]].
+
+***** =WeatherX StationID SkyConditions Args RefreshRate=
+
+      - Works in the same way as =Weather=, but takes an additional argument,
+        a list of pairs from sky conditions to their replacement (typically a
+        unicode string or an icon specification).
+      - Use the variable =skyConditionS= to display the replacement of the
+        corresponding sky condition. All other =Weather= template variables
+        are available as well.
+
+      For example:
+
+      #+begin_src haskell
+        WeatherX "LEBL"
+                 [ ("clear", "🌣")
+                 , ("sunny", "🌣")
+                 , ("mostly clear", "🌤")
+                 , ("mostly sunny", "🌤")
+                 , ("partly sunny", "⛅")
+                 , ("fair", "🌑")
+                 , ("cloudy","☁")
+                 , ("overcast","☁")
+                 , ("partly cloudy", "⛅")
+                 , ("mostly cloudy", "🌧")
+                 , ("considerable cloudiness", "⛈")]
+                 ["-t", "<fn=2><skyConditionS></fn> <tempC>° <rh>%  <windKmh> (<hour>)"
+                 , "-L","10", "-H", "25", "--normal", "black"
+                 , "--high", "lightgoldenrod4", "--low", "darkseagreen4"]
+                 18000
+      #+end_src
+
+      As mentioned, the replacement string can also be an icon specification,
+      such as =("clear", "<icon=weather-clear.xbm/>")=.
+
+*** Other Monitors
+***** =Brightness Args RefreshRate=
+
+      - Aliases to =bright=
+
+      - Args: default monitor arguments, plus the following specif ones:
+
+        - =-D=: directory in =/sys/class/backlight/= with files in it
+          (default: "acpi_video0")
+        - =-C=: file with the current brightness (default: actual_brightness)
+        - =-M=: file with the maximum brightness (default: max_brightness)
+        - =--brightness-icon-pattern=: dynamic string for current brightness
+          in =ipat=.
+
+      - Variables that can be used with the =-t/--template= argument:
+        =vbar=, =percent=, =bar=, =ipat=
+
+      - Default template: =<percent>=
+
+      - Example:
+
+        #+begin_src haskell
+          Run Brightness ["-t", "<bar>"] 60
+        #+end_src
+
+***** =CatInt n filename=
+
+      - Reads and displays an integer from the file whose path is =filename=
+        (especially useful with files in =/sys=).
+
+      - Aliases as =catn= (e.g. =Cat 0= as =cat0=, etc.) so you can have
+        several.
+
+      - Example:
+
+        #+begin_src haskell
+          Run CatInt 0 "/sys/devices/platform/thinkpad_hwmon/fan1_input" [] 50
+        #+end_src
+
+***** =CommandReader "/path/to/program" Alias=
+
+      - Runs the given program, and displays its standard output.
+
+***** =Uptime Args RefreshRate=
+
+      - Aliases to =uptime=
+      - Args: default monitor arguments. The low and high thresholds refer to
+        the number of days.
+      - Variables that can be used with the =-t/--template= argument: =days=,
+        =hours=, =minutes=, =seconds=. The total uptime is the sum of all
+        those fields. You can set the =-S= argument to =True= to add units to
+        the display of those numeric fields.
+      - Default template: =Up: <days>d <hours>h <minutes>m=
+
+***** =UVMeter=
+
+      - Aliases to "uv" + station id. For example: =%uv Brisbane%= or
+        =%uv   Alice Springs%=
+
+      - Args: default monitor arguments, plus:
+
+        - =--useManager= /bool/ : Whether to use one single manager per
+          monitor for managing network connections or create a new one every
+          time a connection is made.
+
+          - Short option: =-m=
+          - Default: True
+
+      - /Reminder:/ Keep the refresh rate high, to avoid making unnecessary
+        requests every time the plug-in is run.
+
+      - Station IDs can be found here:
+        http://www.arpansa.gov.au/uvindex/realtime/xml/uvvalues.xml
+
+      - Example:
+
+        #+begin_src haskell
+          Run UVMeter "Brisbane" ["-H", "3", "-L", "3", "--low", "green", "--high", "red"] 900
+        #+end_src
+
+* Executing External Commands
+
+  In order to execute an external command you can either write the
+  command name in the template, in this case it will be executed
+  without arguments, or you can configure it in the "commands"
+  configuration option list with the Com template command:
+
+  =Com ProgramName Args Alias RefreshRate=
+
+  - ProgramName: the name of the program
+  - Args: the arguments to be passed to the program at execution time
+  - RefreshRate: number of tenths of second between re-runs of the
+    command. A zero or negative rate means that the command will be
+    executed only once.
+  - Alias: a name to be used in the template. If the alias is en empty
+    string the program name can be used in the template.
+
+  E.g.:
+
+  #+begin_src haskell
+    Run Com "uname" ["-s","-r"] "" 0
+  #+end_src
+
+  can be used in the output template as =%uname%= (and xmobar will call
+  /uname/ only once), while
+
+  #+begin_src haskell
+    Run Com "date" ["+\"%a %b %_d %H:%M\""] "mydate" 600
+  #+end_src
+
+  can be used in the output template as =%mydate%=.
+
+  Sometimes, you don't mind if the command executed exits with an
+  error, or you might want to display a custom message in that
+  case. To that end, you can use the =ComX= variant:
+
+    =ComX ProgramName Args ExitMessage Alias RefreshRate=
+
+  Works like =Com=, but displaying =ExitMessage= (a string) if the
+  execution fails. For instance:
+
+  #+begin_src haskell
+    Run ComX "date" ["+\"%a %b %_d %H:%M\""] "N/A" "mydate" 600
+  #+end_src
+
+  will display "N/A" if for some reason the =date= invocation fails.
diff --git a/doc/quick-start.org b/doc/quick-start.org
--- a/doc/quick-start.org
+++ b/doc/quick-start.org
@@ -1,3 +1,5 @@
+#+title: Quick start: using xmobar
+
 Xmobar can either be configured using the configuration language, or
 used as a Haskell library (similar to xmonad) and compiled with your
 specific configuration. For an example of the latter, you can have a
@@ -7,373 +9,461 @@
 There is also an example of a config using the configuration language
 available [[http://github.com/jaor/xmobar/raw/master/examples/xmobar.config][here]].
 
-* Configuration Options
+* Command Line Options
 
-Here are all the global configuration options that you can set within
-the =Config= block in your configuration.
+  xmobar can be either configured with a configuration file or with
+  command line options. In the second case, the command line options will
+  overwrite the corresponding options set in the configuration file.
 
-- =font= Name of the font to be used. Use the =xft:= prefix for XFT
-  fonts.
+  Example:
 
-- =additionalFonts= Haskell-style list of fonts to be used with the
-  =fn=-template. Use the =xft:= prefix for XFT fonts. See also
-  =textOffsets= below. For example:
+  #+begin_src shell
+    xmobar -B white -a right -F blue -t '%LIPB%' -c '[Run Weather "LIPB" [] 36000]'
+  #+end_src
 
-  #+begin_src haskell
-    additionalFonts = [iconFont, altIconFont]
+  This is the list of command line options (the output of =xmobar --help=):
+
+  #+begin_src shell
+    Usage: xmobar [OPTION...] [FILE]
+    Options:
+      -h, -?        --help                 This help
+      -v            --verbose              Emit verbose debugging messages
+      -r            --recompile            Force recompilation
+      -V            --version              Show version information
+      -f font name  --font=font name       Font name
+      -N font name  --add-font=font name   Add to the list of additional fonts
+      -w class      --wmclass=class        X11 WM_CLASS property
+      -n name       --wmname=name          X11 WM_NAME property
+      -B bg color   --bgcolor=bg color     The background color. Default black
+      -F fg color   --fgcolor=fg color     The foreground color. Default grey
+      -i path       --iconroot=path        Root directory for icon pattern paths. Default '.'
+      -A alpha      --alpha=alpha          Transparency: 0 is transparent, 255 is opaque. Default: 255
+      -o            --top                  Place xmobar at the top of the screen
+      -b            --bottom               Place xmobar at the bottom of the screen
+      -d            --dock                 Don't override redirect from WM and function as a dock
+      -a alignsep   --alignsep=alignsep    Separators for left, center and right text
+                                           alignment. Default: '}{'
+      -s char       --sepchar=char         Character used to separate commands in
+                                           the output template. Default '%'
+      -t template   --template=template    Output template
+      -c commands   --commands=commands    List of commands to be executed
+      -C command    --add-command=command  Add to the list of commands to be executed
+      -x screen     --screen=screen        On which X screen number to start
+      -p position   --position=position    Specify position of xmobar. Same syntax as in config file
+      -T [format]   --text[=format]        Write output to stdout
+
+    Mail bug reports and suggestions to <mail@jao.io>
   #+end_src
+* Configuration Options
+*** Global options
+    Here are all the global configuration options that you can set within
+    the =Config= block in your configuration.
 
-- =bgColor= Background color.
+    - =font= Name of the font to be used. Use the =xft:= prefix for XFT
+      fonts.
 
-- =fgColor= Default font color.
+    - =additionalFonts= Haskell-style list of fonts to be used with the
+      =fn=-template. Use the =xft:= prefix for XFT fonts. See also
+      =textOffsets= below. For example:
 
-- =alpha= The transparency. 0 is transparent, 255 is opaque.
+      #+begin_src haskell
+        additionalFonts = [iconFont, altIconFont]
+      #+end_src
 
-- =position= Top, TopH, TopP, TopW, TopSize, Bottom, BottomH,
-  BottomP, BottomW, BottomSize or Static (with x, y, width and height).
+    - =bgColor= Background color.
 
-  TopP and BottomP take 2 arguments: left padding and right padding.
+    - =fgColor= Default font color.
 
-  TopW and BottomW take 2 arguments: an alignment parameter (L for left,
-  C for centered, R for Right) and an integer for the percentage width
-  xmobar window will have in respect to the screen width.
+    - =alpha= The transparency. 0 is transparent, 255 is opaque.
 
-  TopSize and BottomSize take 3 arguments: an alignment parameter, an
-  integer for the percentage width, and an integer for the minimum pixel
-  height that the xmobar window will have.
+    - =position= Top, TopH, TopP, TopW, TopSize, Bottom, BottomH,
+      BottomP, BottomW, BottomSize or Static (with x, y, width and height).
 
-  TopH and BottomH take one argument (Int) which adjusts the bar height.
+      TopP and BottomP take 2 arguments: left padding and right padding.
 
-  For example:
+      TopW and BottomW take 2 arguments: an alignment parameter (L for left,
+      C for centered, R for Right) and an integer for the percentage width
+      xmobar window will have in respect to the screen width.
 
-  #+begin_src haskell
-    position = TopH 30
-  #+end_src
+      TopSize and BottomSize take 3 arguments: an alignment parameter, an
+      integer for the percentage width, and an integer for the minimum pixel
+      height that the xmobar window will have.
 
-  to make a 30 tall bar on the top, or
+      TopH and BottomH take one argument (Int) which adjusts the bar height.
 
-  #+begin_src haskell
-    position = BottomH 30
-  #+end_src
+      For example:
 
-  to make a 30 tall bar on the bottom of the screen.
+      #+begin_src haskell
+        position = TopH 30
+      #+end_src
 
-  #+begin_src haskell
-    position = BottomW C 75
-  #+end_src
+      to make a 30 tall bar on the top, or
 
-  to place xmobar at the bottom, centered with the 75% of the screen
-  width. Or
+      #+begin_src haskell
+        position = BottomH 30
+      #+end_src
 
-  #+begin_src haskell
-    position = BottomP 120 0
-  #+end_src
+      to make a 30 tall bar on the bottom of the screen.
 
-  to place xmobar at the bottom, with 120 pixel indent of the left. Or
+      #+begin_src haskell
+        position = BottomW C 75
+      #+end_src
 
-  #+begin_src haskell
-    position = Static { xpos = 0 , ypos = 0, width = 1024, height = 15 }
-  #+end_src
+      to place xmobar at the bottom, centered with the 75% of the screen
+      width. Or
 
-  or
+      #+begin_src haskell
+        position = BottomP 120 0
+      #+end_src
 
-  #+begin_src haskell
-    position = Top
-  #+end_src
+      to place xmobar at the bottom, with 120 pixel indent of the left. Or
 
-- =textOffset= The vertical offset, in pixels, for the text baseline. If
-  negative or not given, xmobar will try to center text vertically.
+      #+begin_src haskell
+        position = Static { xpos = 0 , ypos = 0, width = 1024, height = 15 }
+      #+end_src
 
-- =textOffsets= A list of vertical offsets, in pixels, for the text
-  baseline, to be used with the each of the fonts in =additionalFonts=
-  (if any). If negative or not given, xmobar will try to center text
-  vertically for that font.
+      or
 
-- =iconOffset= The vertical offset, in pixels, for icons bottom line. If
-  negative or not given, xmobar will try to center icons vertically.
+      #+begin_src haskell
+        position = Top
+      #+end_src
 
-- =lowerOnStart= When True the window is sent the bottom of the window
-  stack initially.
+    - =textOffset= The vertical offset, in pixels, for the text baseline. If
+      negative or not given, xmobar will try to center text vertically.
 
-- =hideOnStart= When set to True the window is initially not mapped,
-  i.e. hidden. It then can be toggled manually (for example using the
-  dbus interface) or automatically (by a plugin) to make it reappear.
+    - =textOffsets= A list of vertical offsets, in pixels, for the text
+      baseline, to be used with the each of the fonts in =additionalFonts=
+      (if any). If negative or not given, xmobar will try to center text
+      vertically for that font.
 
-- =allDesktops= When set to True (the default), xmobar will tell the
-  window manager explicitly to be shown in all desktops, by setting
-  =_NET_WM_DESKTOP= to 0xffffffff.
+    - =iconOffset= The vertical offset, in pixels, for icons bottom line. If
+      negative or not given, xmobar will try to center icons vertically.
 
-- =overrideRedirect= If you're running xmobar in a tiling window
-  manager, you might need to set this option to =False= so that it
-  behaves as a docked application. Defaults to =True=.
+    - =lowerOnStart= When True the window is sent the bottom of the window
+      stack initially.
 
-- =pickBroadest= When multiple displays are available, xmobar will
-  choose by default the first one to place itself. With this flag set to
-  =True= (the default is =False=) it will choose the broadest one
-  instead.
+    - =hideOnStart= When set to True the window is initially not mapped,
+      i.e. hidden. It then can be toggled manually (for example using the
+      dbus interface) or automatically (by a plugin) to make it reappear.
 
-- =persistent= When True the window status is fixed i.e. hiding or
-  revealing is not possible. This option can be toggled at runtime.
-  Defaults to False.
+    - =allDesktops= When set to True (the default), xmobar will tell the
+      window manager explicitly to be shown in all desktops, by setting
+      =_NET_WM_DESKTOP= to 0xffffffff.
 
-- =border= TopB, TopBM, BottomB, BottomBM, FullB, FullBM or NoBorder
-  (default).
+    - =overrideRedirect= If you're running xmobar in a tiling window
+      manager, you might need to set this option to =False= so that it
+      behaves as a docked application. Defaults to =True=.
 
-  TopB, BottomB, FullB take no arguments, and request drawing a border
-  at the top, bottom or around xmobar's window, respectively.
+    - =pickBroadest= When multiple displays are available, xmobar will
+      choose by default the first one to place itself. With this flag set to
+      =True= (the default is =False=) it will choose the broadest one
+      instead.
 
-  TopBM, BottomBM, FullBM take an integer argument, which is the margin,
-  in pixels, between the border of the window and the drawn border.
+    - =persistent= When True the window status is fixed i.e. hiding or
+      revealing is not possible. This option can be toggled at runtime.
+      Defaults to False.
 
-- =borderColor= Border color.
+    - =border= TopB, TopBM, BottomB, BottomBM, FullB, FullBM or NoBorder
+      (default).
 
-- =borderWidth= Border width in pixels.
+      TopB, BottomB, FullB take no arguments, and request drawing a border
+      at the top, bottom or around xmobar's window, respectively.
 
-- =iconRoot= Root folder where icons are stored. For =<icon=path/>= if
-  path start with =/=, =./= or =../= it is interpreted as it is.
-  Otherwise it will have
+      TopBM, BottomBM, FullBM take an integer argument, which is the margin,
+      in pixels, between the border of the window and the drawn border.
 
-  #+begin_src haskell
-    iconRoot ++ "/"
-  #+end_src
+    - =borderColor= Border color.
 
-  prepended to it. Default is =.=.
+    - =borderWidth= Border width in pixels.
 
-- =commands= For setting the options of the programs to run (optional).
+    - =iconRoot= Root folder where icons are stored. For =<icon=path/>= if
+      path start with =/=, =./= or =../= it is interpreted as it is.
+      Otherwise it will have
 
-- =sepChar= The character to be used for indicating commands in the
-  output template (default '%').
+      #+begin_src haskell
+        iconRoot ++ "/"
+      #+end_src
 
-- =alignSep= a 2 character string for aligning text in the output
-  template. The text before the first character will be align to left,
-  the text in between the 2 characters will be centered, and the text
-  after the second character will be align to the right.
+      prepended to it. Default is =.=.
 
-- =template= The output template.
+    - =commands= For setting the options of the programs to run (optional).
 
-- =wmClass= The value for the window's X11 WM_CLASS property. Defaults
-  to "xmobar".
+    - =sepChar= The character to be used for indicating commands in the
+      output template (default '%').
 
-- =wmName= The value for the window's X11 WM_NAME property. Defaults to
-  "xmobar".
+    - =alignSep= a 2 character string for aligning text in the output
+      template. The text before the first character will be align to left,
+      the text in between the 2 characters will be centered, and the text
+      after the second character will be align to the right.
 
-** The Output =template=
+    - =template= The output template.
 
-The output template is how xmobar will end up printing all of your
-configured commands. It must contain at least one command. Xmobar will
-parse the template and search for the command to be executed in the
-=commands= configuration option. First an =alias= will be searched (some
-plugins, such as =Weather= or =Network=, have default aliases, see the
-[[./plugins.org][plugin documentation]]).  After that, the command name will be tried. If a
-command is found, the arguments specified in the =commands= list will be
-used.
+    - =wmClass= The value for the window's X11 ~WM_CLASS~ property. Defaults
+      to "xmobar".
 
-If no command is found in the =commands= list, xmobar will ask the
-operating system to execute a program with the name found in the
-template. If the execution is not successful an error will be reported.
+    - =wmName= The value for the window's X11 ~WM_NAME~ property. Defaults to
+      "xmobar".
 
-The syntax for the output template is as follows:
+    - =textOutput= When True, instead of running as an X11 application,
+      write output to stdout, with optional color escape sequences.  In
+      this mode, icon and action specifications are ignored.  Default is
+      False.
 
-- =%command%= will execute command and print the output. The output may
-  contain markups to change the characters' color.
+    - =textOutputFormat= Plain, Ansi or Pango, to emit, when in text
+      mode, escape color sequences using ANSI controls (for terminals) or
+      pango markup.  Default is Plain.
 
-- =<fc=#FF0000>string</fc>= will print =string= with =#FF0000= color
-  (red). =<fc=#FF0000,#000000>string</fc>= will print =string= in red
-  with a black background (=#000000=). Background absolute offsets can
-  be specified for XFT fonts. =<fc=#FF0000,#000000:0>string</fc>= will
-  have a background matching the bar's height.
+*** The output =template=
 
-- =<box>string</box>= will print string surrounded by a box in the
-  foreground color. The =box= tag accepts several optional arguments to
-  tailor its looks:
+    The output template is how xmobar will end up printing all of your
+    configured commands. It must contain at least one command. Xmobar
+    will parse the template and search for the command to be executed
+    in the =commands= configuration option. First an =alias= will be
+    searched (some plugins, such as =Weather= or =Network=, have default
+    aliases, see the [[./plugins.org][plugin documentation]]).  After that, the command
+    name will be tried. If a command is found, the arguments specified
+    in the =commands= list will be used.
 
-  - =type=: =Top=, =Bottom=, =VBoth= (a single line above or below
-    string, or both), =Left=, =Right=, =HBoth= (single vertical lines),
-    =Full= (a rectangle, the default).
-  - =color=: the color of the box lines.
-  - =width=: the width of the box lines.
-  - =offset=: an alignment char (L, C or R) followed by the amount of
-    pixels to offset the box lines; the alignment denotes the position
-    of the resulting line, with L/R meaning top/bottom for the vertical
-    lines, and left/right for horizontal ones.
-  - =mt=, =mb=, =ml=, =mr= specify margins to be added at the top,
-    bottom, left and right lines.
+    If no command is found in the =commands= list, xmobar will ask the
+    operating system to execute a program with the name found in the
+    template. If the execution is not successful an error will be
+    reported.
 
-  For example, a box underlining its text with a red line of width 2:
+***** Template syntax
 
-  #+begin_src shell
-    <box type=Bottom width=2 color=red>string</box>
-  #+end_src
+      The syntax for the output template is as follows:
 
-  and if you wanted an underline and an overline with a margin of 2
-  pixels either side:
+      - =%command%= will execute command and print the output. The output may
+        contain markups to change the characters' color.
 
-  #+begin_src shell
-    <box type=VBoth mt=2 mb=2>string</box>
-  #+end_src
+      - =<fc=#FF0000>string</fc>= will print =string= with =#FF0000= color
+        (red). =<fc=#FF0000,#000000>string</fc>= will print =string= in red
+        with a black background (=#000000=). Background absolute offsets can
+        be specified for XFT fonts. =<fc=#FF0000,#000000:0>string</fc>= will
+        have a background matching the bar's height.
 
-- =<fn=1>string</fn>= will print =string= with the first font from
-  =additionalFonts=. The index =0= corresponds to the standard font.
+      - =<fn=1>string</fn>= will print =string= with the first font from
+        =additionalFonts=. The index =0= corresponds to the standard font.
 
-- =<hspace=X/>= will insert a blank horizontal space of =X= pixels.
-  For example, to add a blank horizontal space of 123 pixels,
-  =<hspace=123/>= may be used.
+      - =<hspace=X/>= will insert a blank horizontal space of =X= pixels.
+        For example, to add a blank horizontal space of 123 pixels,
+        =<hspace=123/>= may be used.
 
-- =<icon=/path/to/icon.xbm/>= will insert the given bitmap. XPM image
-  format is also supported when compiled with the =with_xpm= flag.
+       - =<box>string</box>= will print string surrounded by a box in the
+         foreground color. The =box= tag accepts several optional arguments to
+         tailor its looks: see next section.
 
-- =<action=`command` button=12345>= will execute given command when
-  clicked with specified buttons. If not specified, button is equal to 1
-  (left mouse button). Using old syntax (without backticks surrounding
-  =command=) will result in =button= attribute being ignored.
+      - =<icon=/path/to/icon.xbm/>= will insert the given bitmap. XPM image
+        format is also supported when compiled with the =with_xpm= flag.
 
-- =<raw=len:str/>= allows the encapsulation of arbitrary text =str=
-  (which must be =len= =Char=s long, where =len= is encoded as a decimal
-  sequence). Careful use of this and =UnsafeStdinReader=, for example,
-  permits window managers to feed xmobar strings with =<action>= tags
-  mixed with un-trusted content (e.g. window titles). For example, if
-  xmobar is invoked as
+      - =<action=`command` button=12345>= will execute given command when
+        clicked with specified buttons. If not specified, button is equal to 1
+        (left mouse button). Using old syntax (without backticks surrounding
+        =command=) will result in =button= attribute being ignored.
 
-  #+begin_src shell
-    xmobar -c "[Run UnsafeStdinReader]" -t "%UnsafeStdinReader%"
-  #+end_src
+      - =<raw=len:str/>= allows the encapsulation of arbitrary text =str=
+        (which must be =len= =Char=s long, where =len= is encoded as a decimal
+        sequence). Careful use of this and =UnsafeStdinReader=, for example,
+        permits window managers to feed xmobar strings with =<action>= tags
+        mixed with un-trusted content (e.g. window titles). For example, if
+        xmobar is invoked as
 
-  and receives on standard input the line
+        #+begin_src shell
+          xmobar -c "[Run UnsafeStdinReader]" -t "%UnsafeStdinReader%"
+        #+end_src
 
-  #+begin_src shell
-    <action=`echo test` button=1><raw=41:<action=`echo mooo` button=1>foo</action>/></action>`
-  #+end_src
+        and receives on standard input the line
 
-  then it will display the text
-  =<action=`echo mooo` button=1>foo</action>=, which, when clicked, will
-  cause =test= to be echoed.
+        #+begin_src shell
+          <action=`echo test` button=1><raw=41:<action=`echo mooo` button=1>foo</action>/></action>`
+        #+end_src
 
-*** Bitmap Icons
+        then it will display the text
+        =<action=`echo mooo` button=1>foo</action>=, which, when clicked, will
+        cause =test= to be echoed.
 
-It's possible to insert in the global templates icon directives of the
-form:
+        See the subsections below for more information on ~<box/>~,
+        ~<icon/>~ and ~<action/>~.
 
-#+begin_src shell
-  <icon=/path/to/bitmap.xbm/>
-#+end_src
+***** Boxes around text
 
-which will produce the expected result. Accepted image formats are XBM
-and XPM (when =with_xpm= flag is enabled). If path does not start with
-=/=, =./=, =../= it will have
+     - =<box>string</box>= will print string surrounded by a box in the
+       foreground color. The =box= tag accepts several optional arguments to
+       tailor its looks:
 
-#+begin_src haskell
-  iconRoot ++ "/"
-#+end_src
+       - =type=: =Top=, =Bottom=, =VBoth= (a single line above or below
+         string, or both), =Left=, =Right=, =HBoth= (single vertical lines),
+         =Full= (a rectangle, the default).
+       - =color=: the color of the box lines.
+       - =width=: the width of the box lines.
+       - =offset=: an alignment char (L, C or R) followed by the amount of
+         pixels to offset the box lines; the alignment denotes the position
+         of the resulting line, with L/R meaning top/bottom for the vertical
+         lines, and left/right for horizontal ones.
+       - =mt=, =mb=, =ml=, =mr= specify margins to be added at the top,
+         bottom, left and right lines.
 
-prepended to it.
+       For example, a box underlining its text with a red line of width 2:
 
-*** Action Directives
+       #+begin_src shell
+         <box type=Bottom width=2 color=red>string</box>
+       #+end_src
 
-It's also possible to use action directives of the form:
+       and if you wanted an underline and an overline with a margin of 2
+       pixels either side:
 
-#+begin_src shell
-  <action=`command` button=12345>
-#+end_src
+       #+begin_src shell
+         <box type=VBoth mt=2 mb=2>string</box>
+       #+end_src
 
-which will be executed when clicked on with specified mouse buttons.
-This tag can be nested, allowing different commands to be run depending
-on button clicked.
+       When xmobar is run in text mode with output format swaybar, box
+       types, colors and widths are valid too, but margins and offsets
+       are ignored.
 
-** The =commands= Configuration Option
+***** Bitmap Icons
 
-The =commands= configuration option is a list of commands information
-and arguments to be used by xmobar when parsing the output template.
-Each member of the list consists in a command prefixed by the =Run=
-keyword. Each command has arguments to control the way xmobar is going
-to execute it.
+      It's possible to insert in the global templates icon directives of the
+      form:
 
-The option consists in a list of commands separated by a comma and
-enclosed by square parenthesis.
+      prepended to it. Default is =.=.
 
-Example:
 
-#+begin_src haskell
-  [Run Memory ["-t","Mem: <usedratio>%"] 10, Run Swap [] 10]
-#+end_src
 
-to run the Memory monitor plugin with the specified template, and the
-swap monitor plugin, with default options, every second. And here's an
-example of a template for the commands above using an icon:
+      #+begin_src shell
+        <icon=/path/to/bitmap.xbm/>
+      #+end_src
 
-#+begin_src haskell
-  template = "<icon=/home/jao/.xmobar/mem.xbm/><memory> <swap>"
-#+end_src
+      which will produce the expected result. Accepted image formats are XBM
+      and XPM (when =with_xpm= flag is enabled). If path does not start with
+      =/=, =./=, =../= it will have
 
-This example will run "xclock" command when date is clicked:
+      #+begin_src haskell
+        iconRoot ++ "/"
+      #+end_src
 
-#+begin_src haskell
-  template = "<action=`xclock`>%date%</action>"
-#+end_src
+      prepended to it.
 
-The only internal available command is =Com= (see below Executing
-External Commands). All other commands are provided by plugins. xmobar
-comes with some plugins, providing a set of system monitors, a standard
-input reader, an Unix named pipe reader, a configurable date plugin, and
-much more: we list all available plugins below.
+      Icons are ignored when xmobar is run in text output mode.
 
-Other commands can be created as plugins with the Plugin infrastructure.
-See below.
-** Running xmobar with =i3status=
+***** Action Directives
 
-xmobar can be used to display information generated by [[http://i3wm.org/i3status/][i3status]], a small
-program that gathers system information and outputs it in formats
-suitable for being displayed by the dzen2 status bar, wmii's status bar
-or xmobar's =StdinReader=. See [[http://i3wm.org/i3status/manpage.html#_using_i3status_with_xmobar][i3status manual]] for further details.
+      It's also possible to use action directives of the form:
 
-** Dynamically sizing xmobar
+      #+begin_src shell
+        <action=`command` button=12345>
+      #+end_src
 
-See [[https://github.com/jaor/xmobar/issues/239#issuecomment-233206552][this idea]] by Jonas Camillus Jeppensen for a way of adapting
-dynamically xmobar's size and run it alongside a system tray widget such
-as trayer or stalonetray (although the idea is not limited to trays,
-really). For your convenience, there is a version of Jonas' script in
-[[../examples/padding-icon.sh][examples/padding-icon.sh]].
+      which will be executed when clicked on with specified mouse
+      buttons.  This tag can be nested, allowing different commands to
+      be run depending on button clicked.
 
-* Command Line Options
+      Actions work also when xmobar is run in text mode and used as
+      the status command of swaybar.
 
-xmobar can be either configured with a configuration file or with
-command line options. In the second case, the command line options will
-overwrite the corresponding options set in the configuration file.
+*** The =commands= configuration option
 
-Example:
+    The =commands= configuration option is a list of commands information
+    and arguments to be used by xmobar when parsing the output template.
+    Each member of the list consists in a command prefixed by the =Run=
+    keyword. Each command has arguments to control the way xmobar is going
+    to execute it.
 
-#+begin_src shell
-  xmobar -B white -a right -F blue -t '%LIPB%' -c '[Run Weather "LIPB" [] 36000]'
-#+end_src
+    The option consists in a list of commands separated by a comma and
+    enclosed by square parenthesis.
 
-This is the list of command line options (the output of =xmobar --help=):
+    Example:
 
-#+begin_src shell
-  Usage: xmobar [OPTION...] [FILE]
-  Options:
-    -h, -?        --help                 This help
-    -v            --verbose              Emit verbose debugging messages
-    -r            --recompile            Force recompilation
-    -V            --version              Show version information
-    -f font name  --font=font name       Font name
-    -N font name  --add-font=font name   Add to the list of additional fonts
-    -w class      --wmclass=class        X11 WM_CLASS property
-    -n name       --wmname=name          X11 WM_NAME property
-    -B bg color   --bgcolor=bg color     The background color. Default black
-    -F fg color   --fgcolor=fg color     The foreground color. Default grey
-    -i path       --iconroot=path        Root directory for icon pattern paths. Default '.'
-    -A alpha      --alpha=alpha          Transparency: 0 is transparent, 255 is opaque. Default: 255
-    -o            --top                  Place xmobar at the top of the screen
-    -b            --bottom               Place xmobar at the bottom of the screen
-    -d            --dock                 Don't override redirect from WM and function as a dock
-    -a alignsep   --alignsep=alignsep    Separators for left, center and right text
-                                         alignment. Default: '}{'
-    -s char       --sepchar=char         Character used to separate commands in
-                                         the output template. Default '%'
-    -t template   --template=template    Output template
-    -c commands   --commands=commands    List of commands to be executed
-    -C command    --add-command=command  Add to the list of commands to be executed
-    -x screen     --screen=screen        On which X screen number to start
-    -p position   --position=position    Specify position of xmobar. Same syntax as in config file
+    #+begin_src haskell
+      [Run Memory ["-t","Mem: <usedratio>%"] 10, Run Swap [] 10]
+    #+end_src
 
-  Mail bug reports and suggestions to <mail@jao.io>
-#+end_src
+    to run the Memory monitor plugin with the specified template, and the
+    swap monitor plugin, with default options, every second. And here's an
+    example of a template for the commands above using an icon:
+
+    #+begin_src haskell
+      template = "<icon=/home/jao/.xmobar/mem.xbm/><memory> <swap>"
+    #+end_src
+
+    This example will run "xclock" command when date is clicked:
+
+    #+begin_src haskell
+      template = "<action=`xclock`>%date%</action>"
+    #+end_src
+
+    The only internal available command is =Com= (see below Executing
+    External Commands). All other commands are provided by plugins. xmobar
+    comes with some plugins, providing a set of system monitors, a standard
+    input reader, an Unix named pipe reader, a configurable date plugin, and
+    much more: we list all available plugins below.
+
+    Other commands can be created as plugins with the Plugin infrastructure.
+    See below.
+
+* Runtime behaviour
+*** Running xmobar with =i3status=
+
+ xmobar can be used to display information generated by [[http://i3wm.org/i3status/][i3status]], a small
+ program that gathers system information and outputs it in formats
+ suitable for being displayed by the dzen2 status bar, wmii's status bar
+ or xmobar's =StdinReader=. See [[http://i3wm.org/i3status/manpage.html#_using_i3status_with_xmobar][i3status manual]] for further details.
+
+*** Dynamically sizing xmobar
+
+ See [[https://github.com/jaor/xmobar/issues/239#issuecomment-233206552][this idea]] by Jonas Camillus Jeppensen for a way of adapting
+ dynamically xmobar's size and run it alongside a system tray widget such
+ as trayer or stalonetray (although the idea is not limited to trays,
+ really). For your convenience, there is a version of Jonas' script in
+ [[../examples/padding-icon.sh][examples/padding-icon.sh]].
+
+*** Signal Handling
+
+    xmobar reacts to ~SIGUSR1~ and ~SIGUSR2~:
+
+    - After receiving ~SIGUSR1~ xmobar moves its position to the next screen.
+
+    - After receiving ~SIGUSR2~ xmobar repositions itself on the current
+      screen.
+* The DBus Interface
+
+  When compiled with the optional =with_dbus= flag, xmobar can be
+  controlled over dbus. All signals defined in [[https://github.com/jaor/xmobar/blob/master/src/Xmobar/System/Signal.hs][src/Signal.hs]] as =data
+  SignalType= can now be sent over dbus to xmobar. Due to current
+  limitations of the implementation only one process of xmobar can
+  acquire the dbus. This is handled on a first-come-first-served
+  basis, meaning that the first process will get the dbus
+  interface. Other processes will run without further problems, yet
+  have no dbus interface.
+
+  - Bus Name: =org.Xmobar.Control=
+  - Object Path: =/org/Xmobar/Control=
+  - Member Name: Any of SignalType, e.g. =string:Reveal=
+  - Interface Name: =org.Xmobar.Control=
+
+  An example using the =dbus-send= command line utility:
+
+  #+begin_src shell
+  dbus-send \
+      --session \
+      --dest=org.Xmobar.Control \
+      --type=method_call \
+      --print-reply \
+      '/org/Xmobar/Control' \
+      org.Xmobar.Control.SendSignal \
+      "string:Toggle 0"
+  #+end_src
+
+  It is also possible to send multiple signals at once:
+
+  #+begin_src shell
+    # send to another screen, reveal and toggle the persistent flag
+    dbus-send [..] \
+        "string:ChangeScreen 0" "string:Reveal 0" "string:TogglePersistent"
+  #+end_src
+
+  The =Toggle=, =Reveal=, and =Hide= signals take an additional integer
+  argument that denotes an initial delay, in tenths of a second,
+  before the command takes effect.
+
+  See [[window-managers.org::*Example of using][Interfacing with window managers]] for an example of how to use
+  the DBus interface from xmonad.
diff --git a/examples/xmobar.config b/examples/xmobar.config
--- a/examples/xmobar.config
+++ b/examples/xmobar.config
@@ -15,6 +15,7 @@
        , iconRoot = "."
        , allDesktops = True
        , overrideRedirect = True
+       , textOutputFormat = Ansi
        , commands = [ Run Weather "EGPF" ["-t","<station>: <tempC>C",
                                           "-L","18","-H","25",
                                           "--normal","green",
diff --git a/examples/xmobar.hs b/examples/xmobar.hs
--- a/examples/xmobar.hs
+++ b/examples/xmobar.hs
@@ -1,6 +1,6 @@
 ------------------------------------------------------------------------------
 -- |
--- Copyright: (c) 2018, 2019 Jose Antonio Ortega Ruiz
+-- Copyright: (c) 2018, 2019, 2022 Jose Antonio Ortega Ruiz
 -- License: BSD3-style (see LICENSE)
 --
 -- Maintainer: jao@gnu.org
@@ -49,6 +49,7 @@
   , iconRoot = "."
   , allDesktops = True
   , overrideRedirect = True
+  , textOutputFormat = Ansi
   , commands = [ Run $ Weather "EGPH" ["-t","<station>: <tempC>C",
                                         "-L","18","-H","25",
                                         "--normal","green",
diff --git a/readme.org b/readme.org
--- a/readme.org
+++ b/readme.org
@@ -1,14 +1,15 @@
-[[http://hackage.haskell.org/package/xmobar][https://img.shields.io/hackage/v/xmobar.svg]]
+#+title: xmobar, a minimalistic status bar
 
-* About
+[[http://hackage.haskell.org/package/xmobar][https://img.shields.io/hackage/v/xmobar.svg]]
 
 Xmobar is a minimalistic status bar. It was originally designed and
-implemented by Andrea Rossato to work with [[http://xmonad.org][xmonad]], but it is actually
-usable with any window manager.
+implemented to work with [[http://xmonad.org][xmonad]], but it is actually usable with any
+window manager in X11 and also (via its text output mode) Wayland.
 
 Xmobar was inspired by the [[http://tuomov.iki.fi/software/][Ion3]] status bar, and supports similar
 features, like dynamic color management, icons, output templates, and
-extensibility through plugins.
+extensibility through plugins.  It is also able to write to standard
+output, in a variety of formats.
 
 These are some xmobar [[file:doc/screenshots][screenshots]] using the author's configuration:
 
@@ -18,160 +19,208 @@
 
 [[file:doc/screenshots/xmobar-exwm.png]]
 
-This is the [[https://xmobar.org/changelog.html][changelog]] for recent releases.
+Check [[./changelog.md][the change log]] for our release history.  We also have an IRC
+channel, ~#xmobar~, at [[ircs://irc.libera.chat][Libera]].
 
 * Installation
-** From your Systems Package Manager
+*** From your system's package manager
 
-Xmobar is probably available from your distributions package manager!
-Most distributions compile xmobar with the =all_extensions= flag, so you
-don't have to.
+    Xmobar is probably available from your distributions package
+    manager!  Most distributions compile xmobar with the =all_extensions=
+    flag, so you don't have to.
 
-*** Arch Linux
+***** Arch Linux
 
-#+begin_src shell
-  pacman -S xmobar
-#+end_src
+  #+begin_src shell
+    pacman -S xmobar
+  #+end_src
 
-*** Debian/Ubuntu based
+***** Debian/Ubuntu based
 
-#+begin_src shell
-  apt install xmobar
-#+end_src
+  #+begin_src shell
+    apt install xmobar
+  #+end_src
 
-*** OpenSUSE
+***** OpenSUSE
 
-#+begin_src shell
-  zypper install xmobar
-#+end_src
+  #+begin_src shell
+    zypper install xmobar
+  #+end_src
 
-*** Void Linux
+***** Void Linux
 
-#+begin_src shell
-  xbps-install xmobar
-#+end_src
+  #+begin_src shell
+    xbps-install xmobar
+  #+end_src
 
-*** Gentoo
-#+begin_src shell
-  emerge --ask xmobar
-#+end_src
+***** Gentoo
+  #+begin_src shell
+    emerge --ask xmobar
+  #+end_src
 
-** Using cabal-install
+*** Using cabal-install
 
-Xmobar is available from [[http://hackage.haskell.org/package/xmobar/][Hackage]], and you can install it using
-=cabal-install=:
+    Xmobar is available from [[http://hackage.haskell.org/package/xmobar/][Hackage]], and you can install it using
+    =cabal-install=:
 
-#+begin_src shell
-  cabal install xmobar
-#+end_src
+    #+begin_src shell
+      cabal install xmobar
+    #+end_src
 
-Starting with version 0.35.1, xmobar now requires at least GHC version
-8.4.x. to build. See [[https://github.com/jaor/xmobar/issues/461][this issue]] for more information.
+    Starting with version 0.35.1, xmobar now requires at least GHC
+    version 8.4.x. to build. See [[https://github.com/jaor/xmobar/issues/461][this issue]] for more information.
 
-See [[file:doc/compiling.org][compiling]] for a list of optional compilation flags that will enable
-some optional plugins. For instance, to install xmobar with all the
-bells and whistles (this is probably what you want), use:
+    See [[file:doc/compiling.org][compiling]] for a list of optional compilation flags that will
+    enable some optional plugins. For instance, to install xmobar with
+    all the bells and whistles (this is probably what you want), use:
 
-#+begin_src shell
-  cabal install xmobar --flags="all_extensions"
-#+end_src
+    #+begin_src shell
+      cabal install xmobar --flags="all_extensions"
+    #+end_src
 
-** From source
+*** From source
 
-See [[file:doc/compiling.org][compiling]].
+    See [[file:doc/compiling.org][compiling]].
 
 * Running xmobar
 
-You can run xmobar with:
+  You can run xmobar with:
 
-#+begin_src shell
-  xmobar /path/to/config &
-#+end_src
+  #+begin_src shell
+    xmobar /path/to/config &
+  #+end_src
 
-or
+  or
 
-#+begin_src shell
-  xmobar &
-#+end_src
+  #+begin_src shell
+    xmobar &
+  #+end_src
 
-if you have the default configuration file saved as
-=$XDG\_CONFIG\_HOME/xmobar/xmobarrc= (defaulting to
-=~/.config/xmobar/xmobarrc=), or =~/.xmobarrc=.
+  if you have the default configuration file saved as
+  =$XDG_CONFIG_HOME/xmobar/xmobarrc= (defaulting to
+  =~/.config/xmobar/xmobarrc=), or =~/.xmobarrc=.
 
-** Signal Handling
+*** Running xmobar in text mode
 
-Since 0.14 xmobar reacts to SIGUSR1 and SIGUSR2:
+    By default, xmobar will run as an X11 application, in a docked
+    window, but it is possible to redirect xmobar's to the standard
+    output, optionally with color escape sequences.  In this mode,
+    xmobar can be run inside a terminal o console, or its output piped
+    to other applications, and there is no need for an X11 display
+    (so, for instance, you could pipe xmobar's output to a Wayland
+    application, such as swaybar.)
 
-- After receiving SIGUSR1 xmobar moves its position to the next screen.
+    To run xmobar in text mode, either pass the =-T= flag to its
+    invocation:
 
-- After receiving SIGUSR2 xmobar repositions itself on the current
-  screen.
+    #+begin_src shell
+      xmobar -T /path/to/config &
+    #+end_src
 
-* Configuration and Further Links
+    or set the parameter =textOutput= to True in its configuration.  You
+    can also specify the format of color escapes, for instance,
+    omitting them altogether with ~Plain~:
 
-- If you want to jump straight into configuring xmobar, head over to the
-  [[./doc/quick-start.org][quick-start]] guide.
+    #+begin_src shell
+      xmobar -TPlain /path/to/config &
+    #+end_src
 
-- If you want to get a detailed overview of all available plugins and
-  monitors, visit the [[./doc/plugins.org][plugins]] file.
+    Other options are ~Ansi~, ~Pango~, and ~Swaybar~.
 
-- If you want to know how to contribute to the xmobar project, check out
-  [[contributing.org][contributing]].
+*** Using xmobar in Wayland with swaybar or waybar
 
-- If you want to write your own plugins, see [[./doc/write-your-own-plugin.org][write-your-own-plugin]].
+    In text mode, xmobar can be told to ouput its information using
+    pango markup for colors and fonts, and it that way you can use it
+    with swaybar or waybar, if you don't have actions or boxes in your
+    template.  Here's a minimal ~bar~ configuration for sway's
+    configuration file:
 
-* Authors and credits
+    #+begin_src conf
+      bar {
+        status_command xmobar -TPango
+        pango_markup enabled
+      }
+    #+end_src
 
-Andrea Rossato originally designed and implemented xmobar up to version
-0.11.1. Since then, it is maintained and developed by [[https://jao.io][jao]], with the help
-of the greater xmobar and Haskell communities.
+    In case you want to use boxes around text or click actions in your
+    template, you can use instead the format ~Swaybar~, which supports
+    both.  This output format follows the JSON /swaybar-protocol/
+    defined by swaybar.  Configure it simply with:
 
-In particular, xmobar incorporates patches by Mohammed Alshiekh, Alex
-Ameen, Axel Angel, Dhananjay Balan, Claudio Bley, Dragos Boca, Ben
-Boeckel, Ivan Brennan, Duncan Burke, Roman Cheplyaka, Patrick Chilton,
-Antoine Eiche, Nathaniel Wesley Filardo, Guy Gastineau, John Goerzen,
-Reto Hablützel, Juraj Hercek, Tomáš Janoušek, Ada Joule, Spencer
-Janssen, Roman Joost, Jochen Keil, Sam Kirby, Lennart Kolmodin,
-Krzysztof Kosciuszkiewicz, Dmitry Kurochkin, Todd Lunter, Vanessa
-McHale, Robert J. Macomber, Dmitry Malikov, David McLean, Joan MIlev,
-Marcin Mikołajczyk, Dino Morelli, Tony Morris, Eric Mrak, Thiago
-Negri, Edward O'Callaghan, Svein Ove, Martin Perner, Jens Petersen,
-Alexander Polakov, Sibi Prabakaran, Pavan Rikhi, Petr Rockai, Andrew
-Emmanuel Rosa, Sackville-West, Amir Saeid, Markus Scherer, Daniel
-Schüssler, Olivier Schneider, Alexander Shabalin, Valentin Shirokov,
-Peter Simons, Alexander Solovyov, Will Song, John Soo, John Soros,
-Felix Springer, Travis Staton, Artem Tarasov, Samuli Thomasson, Edward
-Tjörnhammar, Sergei Trofimovich, Thomas Tuegel, John Tyree, Jan
-Vornberger, Anton Vorontsov, Daniel Wagner, Zev Weiss, Phil Xiaojun
-Hu, Nikolay Yakimov, Edward Z. Yang, Leo Zhang, Norbert Zeh, and
-Michal Zielonka.
+    #+begin_src conf
+      bar {
+        status_command xmobar -TSwaybar
+      }
+    #+end_src
 
-** Thanks
+* Configuration and further Links
 
-*Andrea Rossato*:
+  - If you want to jump straight into running xmobar, head over to the
+    [[./doc/quick-start.org][quick start guide]].
 
-Thanks to Robert Manea and Spencer Janssen for their help in
-understanding how X works. They gave me suggestions on how to solve many
-problems with xmobar.
+  - If you want to get a detailed overview of all available plugins and
+    monitors, visit the [[./doc/plugins.org][plugins index]].
 
-Thanks to Claus Reinke for make me understand existential types (or at
-least for letting me think I grasp existential types...;-).
+  - [[./doc/window-managers.org][Interfacing with window managers]] describes plugins and extensions
+    for interacting with xmobar from your window manager.
 
-*jao*:
+  - If you want to know how to contribute to the xmobar project, check out
+    [[contributing.org][contributing]].
 
-Thanks to Andrea for creating xmobar in the first place, and for giving
-me the chance to contribute.
+  - If you want to write your own plugins, see [[./doc/write-your-own-plugin.org][Write your own plugin]].
 
-* Related
+  - To understand the internal mysteries of xmobar you may try reading
+    [[https://wiki.haskell.org/X_window_programming_in_Haskell][this tutorial]] on X Window Programming in Haskell.
 
-- To understand the internal mysteries of xmobar you may try reading
-  [[https://wiki.haskell.org/X_window_programming_in_Haskell][this tutorial]] on X Window Programming in Haskell.
+* Authors and credits
 
+  Andrea Rossato originally designed and implemented xmobar up to
+  version 0.11.1. Since then, it is maintained and developed by [[https://jao.io][jao]],
+  with the help of the greater xmobar and Haskell communities.
+
+  In particular, xmobar incorporates patches by Mohammed Alshiekh,
+  Alex Ameen, Axel Angel, Dhananjay Balan, Claudio Bley, Dragos Boca,
+  Ben Boeckel, Ivan Brennan, Duncan Burke, Roman Cheplyaka, Patrick
+  Chilton, Antoine Eiche, Nathaniel Wesley Filardo, Guy Gastineau,
+  John Goerzen, Reto Hablützel, Juraj Hercek, Tomáš Janoušek, Ada
+  Joule, Spencer Janssen, Roman Joost, Pavel Kalugin, Jochen Keil, Sam
+  Kirby, Lennart Kolmodin, Krzysztof Kosciuszkiewicz, Dmitry
+  Kurochkin, Todd Lunter, Vanessa McHale, Robert J. Macomber, Dmitry
+  Malikov, David McLean, Joan MIlev, Marcin Mikołajczyk, Dino Morelli,
+  Tony Morris, Eric Mrak, Thiago Negri, Edward O'Callaghan, Svein Ove,
+  Martin Perner, Jens Petersen, Alexander Polakov, Sibi Prabakaran,
+  Pavan Rikhi, Petr Rockai, Andrew Emmanuel Rosa, Sackville-West, Amir
+  Saeid, Markus Scherer, Daniel Schüssler, Olivier Schneider,
+  Alexander Shabalin, Valentin Shirokov, Peter Simons, Alexander
+  Solovyov, Will Song, John Soo, John Soros, Felix Springer, Travis
+  Staton, Artem Tarasov, Samuli Thomasson, Edward Tjörnhammar, Sergei
+  Trofimovich, Thomas Tuegel, John Tyree, Jan Vornberger, Anton
+  Vorontsov, Daniel Wagner, Zev Weiss, Phil Xiaojun Hu, Nikolay
+  Yakimov, Edward Z. Yang, Leo Zhang, Norbert Zeh, and Michal
+  Zielonka.
+
+*** Thanks
+
+  *Andrea Rossato*:
+
+  Thanks to Robert Manea and Spencer Janssen for their help in
+  understanding how X works. They gave me suggestions on how to solve many
+  problems with xmobar.
+
+  Thanks to Claus Reinke for make me understand existential types (or at
+  least for letting me think I grasp existential types...;-).
+
+  *jao*:
+
+  Thanks to Andrea for creating xmobar in the first place, and for giving
+  me the chance to contribute.
+
+
 * License
 
 This software is released under a BSD-style license. See [[https://github.com/jaor/xmobar/raw/master/license][license]] for
 more details.
 
-Copyright © 2010-2020 Jose Antonio Ortega Ruiz
+Copyright © 2010-2022 Jose Antonio Ortega Ruiz
 
 Copyright © 2007-2010 Andrea Rossato
diff --git a/src/Xmobar.hs b/src/Xmobar.hs
--- a/src/Xmobar.hs
+++ b/src/Xmobar.hs
@@ -3,7 +3,7 @@
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Xmobar
--- Copyright   :  (c) 2011, 2012, 2013, 2014, 2015, 2017, 2018, 2019 Jose Antonio Ortega Ruiz
+-- Copyright   :  (c) 2011, 2012, 2013, 2014, 2015, 2017, 2018, 2019, 2022 Jose Antonio Ortega Ruiz
 --                (c) 2007 Andrea Rossato
 -- License     :  BSD-style (see LICENSE)
 --
@@ -51,9 +51,9 @@
 
 import Xmobar.Run.Runnable
 import Xmobar.Run.Exec
-import Xmobar.Run.Command
 import Xmobar.Config.Types
 import Xmobar.Config.Parse
+import Xmobar.Plugins.Command
 import Xmobar.Plugins.BufferedPipeReader
 import Xmobar.Plugins.CommandReader
 import Xmobar.Plugins.Date
diff --git a/src/Xmobar/App/Config.hs b/src/Xmobar/App/Config.hs
--- a/src/Xmobar/App/Config.hs
+++ b/src/Xmobar/App/Config.hs
@@ -1,7 +1,7 @@
 ------------------------------------------------------------------------------
 -- |
 -- Module: Xmobar.Config.Defaults
--- Copyright: (c) 2018, 2019, 2020 Jose Antonio Ortega Ruiz
+-- Copyright: (c) 2018, 2019, 2020, 2022 Jose Antonio Ortega Ruiz
 -- License: BSD3-style (see LICENSE)
 --
 -- Maintainer: jao@gnu.org
@@ -65,6 +65,8 @@
                         "<fc=#00FF00>%uname%</fc> * <fc=#FF0000>%theDate%</fc>"
            , verbose = False
            , signal = SignalChan Nothing
+           , textOutput = False
+           , textOutputFormat = Plain
            }
 
 -- | Return the path to the xmobar data directory.  This directory is
diff --git a/src/Xmobar/App/EventLoop.hs b/src/Xmobar/App/EventLoop.hs
deleted file mode 100644
--- a/src/Xmobar/App/EventLoop.hs
+++ /dev/null
@@ -1,283 +0,0 @@
-{-# LANGUAGE CPP #-}
-
-------------------------------------------------------------------------------
--- |
--- Module: Xmobar.X11.EventLoop
--- Copyright: (c) 2018, 2020 Jose Antonio Ortega Ruiz
--- License: BSD3-style (see LICENSE)
---
--- Maintainer: jao@gnu.org
--- Stability: unstable
--- Portability: portable
--- Created: Sat Nov 24, 2018 19:40
---
---
--- Event loop
---
-------------------------------------------------------------------------------
-
-
-module Xmobar.App.EventLoop
-    ( startLoop
-    , startCommand
-    , newRefreshLock
-    , refreshLock
-    ) where
-
-import Prelude hiding (lookup)
-import Graphics.X11.Xlib hiding (textExtents, textWidth)
-import Graphics.X11.Xlib.Extras
-import Graphics.X11.Xinerama
-import Graphics.X11.Xrandr
-
-import Control.Arrow ((&&&))
-import Control.Monad.Reader
-import Control.Concurrent
-import Control.Concurrent.Async (Async, async)
-import Control.Concurrent.STM
-import Control.Exception (bracket_, handle, SomeException(..))
-import Data.Bits
-import Data.Map hiding (foldr, map, filter)
-import Data.Maybe (fromJust, isJust)
-import qualified Data.List.NonEmpty as NE
-
-import Xmobar.System.Signal
-import Xmobar.Config.Types (persistent, position, iconRoot, Config, Align(..), XPosition(..))
-import Xmobar.Run.Exec
-import Xmobar.Run.Runnable
-import Xmobar.X11.Actions
-import Xmobar.X11.Parsers
-import Xmobar.X11.Window
-import Xmobar.X11.Text
-import Xmobar.X11.Draw
-import Xmobar.X11.Bitmap as Bitmap
-import Xmobar.X11.Types
-import Xmobar.System.Utils (safeIndex)
-
-#ifndef THREADED_RUNTIME
-import Xmobar.X11.Events(nextEvent')
-#endif
-
-#ifdef XFT
-import Graphics.X11.Xft
-#endif
-
-#ifdef DBUS
-import Xmobar.System.DBus
-#endif
-
-runX :: XConf -> X () -> IO ()
-runX xc f = runReaderT f xc
-
-newRefreshLock :: IO (TMVar ())
-newRefreshLock = newTMVarIO ()
-
-refreshLock :: TMVar () -> IO a -> IO a
-refreshLock var = bracket_ lock unlock
-    where
-        lock = atomically $ takeTMVar var
-        unlock = atomically $ putTMVar var ()
-
-refreshLockT :: TMVar () -> STM a -> STM a
-refreshLockT var action = do
-    takeTMVar var
-    r <- action
-    putTMVar var ()
-    return r
-
--- | Starts the main event loop and threads
-startLoop :: XConf
-          -> TMVar SignalType
-          -> TMVar ()
-          -> [[([Async ()], TVar String)]]
-          -> IO ()
-startLoop xcfg@(XConf _ _ w _ _ _ _) sig pauser vs = do
-#ifdef XFT
-    xftInitFtLibrary
-#endif
-    tv <- newTVarIO []
-    _ <- forkIO (handle (handler "checker") (checker tv [] vs sig pauser))
-#ifdef THREADED_RUNTIME
-    _ <- forkOS (handle (handler "eventer") (eventer sig))
-#else
-    _ <- forkIO (handle (handler "eventer") (eventer sig))
-#endif
-#ifdef DBUS
-    runIPC sig
-#endif
-    eventLoop tv xcfg [] sig
-  where
-    handler thing (SomeException e) =
-      void $ putStrLn ("Thread " ++ thing ++ " failed: " ++ show e)
-    -- Reacts on events from X
-    eventer signal =
-      allocaXEvent $ \e -> do
-        dpy <- openDisplay ""
-        xrrSelectInput dpy (defaultRootWindow dpy) rrScreenChangeNotifyMask
-        selectInput dpy w (exposureMask .|. structureNotifyMask .|. buttonPressMask)
-
-        forever $ do
-#ifdef THREADED_RUNTIME
-          nextEvent dpy e
-#else
-          nextEvent' dpy e
-#endif
-          ev <- getEvent e
-          case ev of
-            ConfigureEvent {} -> atomically $ putTMVar signal Reposition
-            ExposeEvent {} -> atomically $ putTMVar signal Wakeup
-            RRScreenChangeNotifyEvent {} -> atomically $ putTMVar signal Reposition
-            ButtonEvent {} -> atomically $
-                   putTMVar signal (Action (ev_button ev) (fi $ ev_x ev))
-            _ -> return ()
-
--- | Send signal to eventLoop every time a var is updated
-checker :: TVar [String]
-           -> [String]
-           -> [[([Async ()], TVar String)]]
-           -> TMVar SignalType
-           -> TMVar ()
-           -> IO ()
-checker tvar ov vs signal pauser = do
-      nval <- atomically $ refreshLockT pauser $ do
-              nv <- mapM concatV vs
-              guard (nv /= ov)
-              writeTVar tvar nv
-              return nv
-      atomically $ putTMVar signal Wakeup
-      checker tvar nval vs signal pauser
-    where
-      concatV = fmap concat . mapM (readTVar . snd)
-
-
--- | Continuously wait for a signal from a thread or a interrupt handler
-eventLoop :: TVar [String]
-             -> XConf
-             -> [([Action], Position, Position)]
-             -> TMVar SignalType
-             -> IO ()
-eventLoop tv xc@(XConf d r w fs vos is cfg) as signal = do
-      typ <- atomically $ takeTMVar signal
-      case typ of
-         Wakeup -> do
-            str <- updateString cfg tv
-            xc' <- updateCache d w is (iconRoot cfg) str >>=
-                     \c -> return xc { iconS = c }
-            as' <- updateActions xc r str
-            runX xc' $ drawInWin r str
-            eventLoop tv xc' as' signal
-
-         Reposition ->
-            reposWindow cfg
-
-         ChangeScreen -> do
-            ncfg <- updateConfigPosition cfg
-            reposWindow ncfg
-
-         Hide   t -> hide   (t*100*1000)
-         Reveal t -> reveal (t*100*1000)
-         Toggle t -> toggle t
-
-         TogglePersistent -> eventLoop
-            tv xc { config = cfg { persistent = not $ persistent cfg } } as signal
-
-         Action but x -> action but x
-
-    where
-        isPersistent = not $ persistent cfg
-
-        hide t
-            | t == 0 =
-                when isPersistent (hideWindow d w) >> eventLoop tv xc as signal
-            | otherwise = do
-                void $ forkIO
-                     $ threadDelay t >> atomically (putTMVar signal $ Hide 0)
-                eventLoop tv xc as signal
-
-        reveal t
-            | t == 0 = do
-                when isPersistent (showWindow r cfg d w)
-                eventLoop tv xc as signal
-            | otherwise = do
-                void $ forkIO
-                     $ threadDelay t >> atomically (putTMVar signal $ Reveal 0)
-                eventLoop tv xc as signal
-
-        toggle t = do
-            ismapped <- isMapped d w
-            atomically (putTMVar signal $ if ismapped then Hide t else Reveal t)
-            eventLoop tv xc as signal
-
-        reposWindow rcfg = do
-          r' <- repositionWin d w (NE.head fs) rcfg
-          eventLoop tv (XConf d r' w fs vos is rcfg) as signal
-
-        updateConfigPosition ocfg =
-          case position ocfg of
-            OnScreen n o -> do
-              srs <- getScreenInfo d
-              return (if n == length srs
-                       then
-                        (ocfg {position = OnScreen 1 o})
-                       else
-                        (ocfg {position = OnScreen (n+1) o}))
-            o -> return (ocfg {position = OnScreen 1 o})
-
-        action button x = do
-          mapM_ runAction $
-            filter (\(Spawn b _) -> button `elem` b) $
-            concatMap (\(a,_,_) -> a) $
-            filter (\(_, from, to) -> x >= from && x <= to) as
-          eventLoop tv xc as signal
-
--- $command
-
--- | Runs a command as an independent thread and returns its Async handles
--- and the TVar the command will be writing to.
-startCommand :: TMVar SignalType
-             -> (Runnable,String,String)
-             -> IO ([Async ()], TVar String)
-startCommand sig (com,s,ss)
-    | alias com == "" = do var <- newTVarIO is
-                           atomically $ writeTVar var (s ++ ss)
-                           return ([], var)
-    | otherwise = do var <- newTVarIO is
-                     let cb str = atomically $ writeTVar var (s ++ str ++ ss)
-                     a1 <- async $ start com cb
-                     a2 <- async $ trigger com $ maybe (return ())
-                                                 (atomically . putTMVar sig)
-                     return ([a1, a2], var)
-    where is = s ++ "Updating..." ++ ss
-
-updateString :: Config -> TVar [String]
-                -> IO [[(Widget, TextRenderInfo, Int, Maybe [Action])]]
-updateString conf v = do
-  s <- readTVarIO v
-  let l:c:r:_ = s ++ repeat ""
-  liftIO $ mapM (parseString conf) [l, c, r]
-
-updateActions :: XConf -> Rectangle -> [[(Widget, TextRenderInfo, Int, Maybe [Action])]]
-                 -> IO [([Action], Position, Position)]
-updateActions conf (Rectangle _ _ wid _) ~[left,center,right] = do
-  let (d,fs) = (display &&& fontListS) conf
-      strLn :: [(Widget, TextRenderInfo, Int, Maybe [Action])] -> IO [(Maybe [Action], Position, Position)]
-      strLn  = liftIO . mapM getCoords
-      iconW i = maybe 0 Bitmap.width (lookup i $ iconS conf)
-      getCoords (Text s,_,i,a) = textWidth d (safeIndex fs i) s >>= \tw -> return (a, 0, fi tw)
-      getCoords (Icon s,_,_,a) = return (a, 0, fi $ iconW s)
-      getCoords (Hspace w,_,_,a) = return (a, 0, fi w)
-      partCoord off xs = map (\(a, x, x') -> (fromJust a, x, x')) $
-                         filter (\(a, _,_) -> isJust a) $
-                         scanl (\(_,_,x') (a,_,w') -> (a, x', x' + w'))
-                               (Nothing, 0, off)
-                               xs
-      totSLen = foldr (\(_,_,len) -> (+) len) 0
-      remWidth xs = fi wid - totSLen xs
-      offs = 1
-      offset a xs = case a of
-                     C -> (remWidth xs + offs) `div` 2
-                     R -> remWidth xs
-                     L -> offs
-  fmap concat $ mapM (\(a,xs) ->
-                       (\xs' -> partCoord (offset a xs') xs') <$> strLn xs) $
-                     zip [L,C,R] [left,center,right]
diff --git a/src/Xmobar/App/Main.hs b/src/Xmobar/App/Main.hs
--- a/src/Xmobar/App/Main.hs
+++ b/src/Xmobar/App/Main.hs
@@ -1,7 +1,7 @@
 ------------------------------------------------------------------------------
 -- |
 -- Module: Xmobar.App.Main
--- Copyright: (c) 2018, 2019, 2020 Jose Antonio Ortega Ruiz
+-- Copyright: (c) 2018, 2019, 2020, 2022 Jose Antonio Ortega Ruiz
 -- License: BSD3-style (see LICENSE)
 --
 -- Maintainer: jao@gnu.org
@@ -17,66 +17,29 @@
 
 module Xmobar.App.Main (xmobar, xmobarMain, configFromArgs) where
 
-import Control.Concurrent.Async (Async, cancel)
-import Control.Concurrent.STM (newEmptyTMVarIO)
-import Control.Exception (bracket)
-import Control.Monad (unless)
 
-import Data.Foldable (for_)
-import qualified Data.Map as Map
 import Data.List (intercalate)
-import Data.Maybe (isJust)
 import System.Posix.Process (executeFile)
 import System.Environment (getArgs)
 import System.FilePath ((</>), takeBaseName, takeDirectory, takeExtension)
 import Text.Parsec.Error (ParseError)
-import Data.List.NonEmpty (NonEmpty(..))
 
-import Graphics.X11.Xlib
+import Control.Monad (unless)
 
+import Xmobar.App.Config
 import Xmobar.Config.Types
 import Xmobar.Config.Parse
-import Xmobar.System.Signal (setupSignalHandler, withDeferSignals)
-import Xmobar.Run.Template
-import Xmobar.X11.Types
-import Xmobar.X11.Text
-import Xmobar.X11.Window
+import Xmobar.X11.Loop (x11Loop)
+import Xmobar.Text.Loop (textLoop)
 import Xmobar.App.Opts (recompileFlag, verboseFlag, getOpts, doOpts)
-import Xmobar.App.EventLoop (startLoop, startCommand, newRefreshLock, refreshLock)
 import Xmobar.App.Compile (recompile, trace)
-import Xmobar.App.Config
-import Xmobar.App.Timer (withTimer)
 
 xmobar :: Config -> IO ()
-xmobar conf = withDeferSignals $ do
-  initThreads
-  d <- openDisplay ""
-  fs    <- initFont d (font conf)
-  fl    <- mapM (initFont d) (additionalFonts conf)
-  cls   <- mapM (parseTemplate (commands conf) (sepChar conf))
-                (splitTemplate (alignSep conf) (template conf))
-  let confSig = unSignalChan (signal conf)
-  sig   <- maybe newEmptyTMVarIO pure confSig
-  unless (isJust confSig) $ setupSignalHandler sig
-  refLock <- newRefreshLock
-  withTimer (refreshLock refLock) $
-    bracket (mapM (mapM $ startCommand sig) cls)
-            cleanupThreads
-            $ \vars -> do
-      (r,w) <- createWin d fs conf
-      let ic = Map.empty
-          to = textOffset conf
-          ts = textOffsets conf ++ replicate (length fl) (-1)
-      startLoop (XConf d r w (fs :| fl) (to :| ts) ic conf) sig refLock vars
+xmobar cfg = if textOutput cfg then textLoop cfg else x11Loop cfg
 
 configFromArgs :: Config -> IO Config
 configFromArgs cfg = getArgs >>= getOpts >>= doOpts cfg . fst
 
-cleanupThreads :: [[([Async ()], a)]] -> IO ()
-cleanupThreads vars =
-  for_ (concat vars) $ \(asyncs, _) ->
-    for_ asyncs cancel
-
 buildLaunch :: [String] -> Bool -> Bool -> String -> ParseError -> IO ()
 buildLaunch args verb force p e = do
   let exec = takeBaseName p
@@ -110,5 +73,6 @@
     Just p -> do r <- readConfig defaultConfig p
                  case r of
                    Left e ->
-                     buildLaunch (filter (/= p) args) (verboseFlag flags) (recompileFlag flags) p e
+                     buildLaunch (filter (/= p) args) (verboseFlag flags)
+                                 (recompileFlag flags) p e
                    Right (c, defs) -> doOpts c flags >>= xmobar' defs
diff --git a/src/Xmobar/App/Opts.hs b/src/Xmobar/App/Opts.hs
--- a/src/Xmobar/App/Opts.hs
+++ b/src/Xmobar/App/Opts.hs
@@ -1,7 +1,7 @@
 ------------------------------------------------------------------------------
 -- |
 -- Module: Xmobar.App.Opts
--- Copyright: (c) 2018, 2019, 2020 Jose Antonio Ortega Ruiz
+-- Copyright: (c) 2018, 2019, 2020, 2022 Jose Antonio Ortega Ruiz
 -- License: BSD3-style (see LICENSE)
 --
 -- Maintainer: jao@gnu.org
@@ -14,7 +14,10 @@
 --
 ------------------------------------------------------------------------------
 
-module Xmobar.App.Opts (recompileFlag, verboseFlag, getOpts, doOpts) where
+module Xmobar.App.Opts ( recompileFlag
+                       , verboseFlag
+                       , getOpts
+                       , doOpts) where
 
 import Control.Monad (when)
 import System.Console.GetOpt
@@ -30,24 +33,25 @@
           | Verbose
           | Recompile
           | Version
-          | Font       String
-          | AddFont    String
-          | BgColor    String
-          | FgColor    String
-          | Alpha      String
+          | TextOutput (Maybe String)
+          | Font String
+          | AddFont String
+          | BgColor String
+          | FgColor String
+          | Alpha String
           | T
           | B
           | D
-          | AlignSep   String
-          | Commands   String
+          | AlignSep String
+          | Commands String
           | AddCommand String
-          | SepChar    String
-          | Template   String
-          | OnScr      String
-          | IconRoot   String
-          | Position   String
-          | WmClass    String
-          | WmName     String
+          | SepChar String
+          | Template String
+          | OnScr String
+          | IconRoot String
+          | Position String
+          | WmClass String
+          | WmName String
        deriving (Show, Eq)
 
 options :: [OptDescr Opts]
@@ -56,8 +60,11 @@
     , Option "v" ["verbose"] (NoArg Verbose) "Emit verbose debugging messages"
     , Option "r" ["recompile"] (NoArg Recompile) "Force recompilation"
     , Option "V" ["version"] (NoArg Version) "Show version information"
+    , Option "T" ["text"] (OptArg TextOutput "color")
+             "Write text-only output to stdout. Plain/Ansi/Pango/Swaybar"
     , Option "f" ["font"] (ReqArg Font "font name") "Font name"
-    , Option "N" ["add-font"] (ReqArg AddFont "font name") "Add to the list of additional fonts"
+    , Option "N" ["add-font"] (ReqArg AddFont "font name")
+             "Add to the list of additional fonts"
     , Option "w" ["wmclass"] (ReqArg WmClass "class") "X11 WM_CLASS property"
     , Option "n" ["wmname"] (ReqArg WmName "name") "X11 WM_NAME property"
     , Option "B" ["bgcolor"] (ReqArg BgColor "bg color" )
@@ -106,7 +113,7 @@
 
 info :: String
 info = "xmobar " ++ showVersion version
-        ++ "\n (C) 2010 - 2020 Jose A Ortega Ruiz"
+        ++ "\n (C) 2010 - 2022 Jose A Ortega Ruiz"
         ++ "\n (C) 2007 - 2010 Andrea Rossato\n "
         ++ mail ++ "\n" ++ license ++ "\n"
 
@@ -127,6 +134,10 @@
     Help -> doOpts' conf
     Version -> doOpts' conf
     Recompile -> doOpts' conf
+    TextOutput s -> doOpts' $ case s of
+                                Just fmt -> conf {textOutput = True,
+                                                  textOutputFormat = read fmt}
+                                Nothing -> conf {textOutput = True}
     Verbose -> doOpts' (conf {verbose = True})
     Font s -> doOpts' (conf {font = s})
     AddFont s -> doOpts' (conf {additionalFonts = additionalFonts conf ++ [s]})
diff --git a/src/Xmobar/App/Timer.hs b/src/Xmobar/App/Timer.hs
deleted file mode 100644
--- a/src/Xmobar/App/Timer.hs
+++ /dev/null
@@ -1,221 +0,0 @@
-{-# LANGUAGE LambdaCase #-}
-------------------------------------------------------------------------------
--- |
--- Module: Xmobar.App.Timer
--- Copyright: (c) 2019, 2020 Tomáš Janoušek
--- License: BSD3-style (see LICENSE)
---
--- Maintainer: Tomáš Janoušek <tomi@nomi.cz>
--- Stability: unstable
---
--- Timer coalescing for recurring actions.
---
-------------------------------------------------------------------------------
-
-module Xmobar.App.Timer
-    ( doEveryTenthSeconds
-    , tenthSeconds
-    , withTimer
-    ) where
-
-import Control.Concurrent (threadDelay)
-import Control.Concurrent.Async (withAsync)
-import Control.Concurrent.STM
-import Control.Exception
-import Control.Monad (forever, forM, guard)
-import Data.Foldable (foldrM, for_)
-import Data.Int (Int64)
-import Data.Map (Map)
-import qualified Data.Map as M
-import Data.Maybe (isJust, fromJust)
-import Data.Time.Clock.POSIX (getPOSIXTime)
-import Data.Unique
-import System.IO.Unsafe (unsafePerformIO)
-
-type Periods = Map Unique Period
-
-data Tick = Tick (TMVar ()) | UnCoalesce
-
-data Period = Period { rate :: Int64, next :: Int64, tick :: TMVar Tick }
-
-data UnCoalesceException = UnCoalesceException deriving Show
-instance Exception UnCoalesceException
-
-{-# NOINLINE periodsVar #-}
-periodsVar :: TVar (Maybe Periods)
-periodsVar = unsafePerformIO $ newTVarIO Nothing
-
-now :: IO Int64
-now = do
-    posix <- getPOSIXTime
-    return $ floor (10 * posix)
-
-newPeriod :: Int64 -> IO (Unique, Period)
-newPeriod r = do
-    u <- newUnique
-    t <- now
-    v <- newEmptyTMVarIO
-    let t' = t - t `mod` r
-    return (u, Period { rate = r, next = t', tick = v })
-
--- | Perform a given action every N tenths of a second.
---
--- The timer is aligned (coalesced) with other timers to minimize the number
--- of wakeups and unnecessary redraws. If the action takes too long (one
--- second or when the next timer is due), coalescing is disabled for it and it
--- falls back to periodic sleep.
-doEveryTenthSeconds :: Int -> IO () -> IO ()
-doEveryTenthSeconds r action =
-    doEveryTenthSecondsCoalesced r action `catch` \UnCoalesceException ->
-        doEveryTenthSecondsSleeping r action
-
--- | Perform a given action every N tenths of a second,
--- coalesce with other timers using a given Timer instance.
-doEveryTenthSecondsCoalesced :: Int -> IO () -> IO ()
-doEveryTenthSecondsCoalesced r action = do
-    (u, p) <- newPeriod (fromIntegral r)
-    bracket_ (push u p) (pop u) $ forever $ bracket (wait p) done $ const action
-    where
-        push u p = atomically $ modifyTVar' periodsVar $ \case
-            Just periods -> Just $ M.insert u p periods
-            Nothing -> throw UnCoalesceException
-        pop u = atomically $ modifyTVar' periodsVar $ \case
-            Just periods -> Just $ M.delete u periods
-            Nothing -> Nothing
-
-        wait p = atomically (takeTMVar $ tick p) >>= \case
-            Tick doneVar -> return doneVar
-            UnCoalesce -> throwIO UnCoalesceException
-        done doneVar = atomically $ putTMVar doneVar ()
-
--- | Perform a given action every N tenths of a second,
--- making no attempt to synchronize with other timers.
-doEveryTenthSecondsSleeping :: Int -> IO () -> IO ()
-doEveryTenthSecondsSleeping r action = go
-    where go = action >> tenthSeconds r >> go
-
--- | Sleep for a given amount of tenths of a second.
---
--- (Work around the Int max bound: since threadDelay takes an Int, it
--- is not possible to set a thread delay grater than about 45 minutes.
--- With a little recursion we solve the problem.)
-tenthSeconds :: Int -> IO ()
-tenthSeconds s | s >= x = do threadDelay (x * 100000)
-                             tenthSeconds (s - x)
-               | otherwise = threadDelay (s * 100000)
-               where x = (maxBound :: Int) `div` 100000
-
--- | Start the timer coordination thread and perform a given IO action (this
--- is meant to surround the entire xmobar execution), terminating the timer
--- thread afterwards.
---
--- Additionally, if the timer thread fails, individual
--- 'doEveryTenthSecondsCoalesced' invocations that are waiting to be
--- coordinated by it are notified to fall back to periodic sleeping.
---
--- The timer thread _will_ fail immediately when running in a non-threaded
--- RTS.
-withTimer :: (IO () -> IO ()) -> IO a -> IO a
-withTimer pauseRefresh action =
-    withAsync (timerThread `finally` cleanup) $ const action
-    where
-        timerThread = do
-            atomically $ writeTVar periodsVar $ Just M.empty
-            timerLoop pauseRefresh
-
-        cleanup = atomically $ readTVar periodsVar >>= \case
-            Just periods -> do
-                for_ periods unCoalesceTimer'
-                writeTVar periodsVar Nothing
-            Nothing -> return ()
-
-timerLoop :: (IO () -> IO ()) -> IO ()
-timerLoop pauseRefresh = forever $ do
-    tNow <- now
-    (toFire, tMaybeNext) <- atomically $ do
-        periods <- fromJust <$> readTVar periodsVar
-        let toFire = timersToFire tNow periods
-        let periods' = advanceTimers tNow periods
-        let tMaybeNext = nextFireTime periods'
-        writeTVar periodsVar $ Just periods'
-        return (toFire, tMaybeNext)
-    pauseRefresh $ do
-        -- To avoid multiple refreshes, pause refreshing for up to 1 second,
-        -- fire timers and wait for them to finish (update their text).
-        -- Those that need more time (e.g. weather monitors) will be dropped
-        -- from timer coalescing and will fall back to periodic sleep.
-        timeoutVar <- registerDelay $ case tMaybeNext of
-            Just tNext -> fromIntegral ((tNext - tNow) `max` 10) * 100000
-            Nothing -> 1000000
-        fired <- fireTimers toFire
-        timeouted <- waitForTimers timeoutVar fired
-        unCoalesceTimers timeouted
-    delayUntilNextFire
-
-advanceTimers :: Int64 -> Periods -> Periods
-advanceTimers t = M.map advance
-    where
-        advance p | next p <= t = p { next = t - t `mod` rate p + rate p }
-                  | otherwise = p
-
-timersToFire :: Int64 -> Periods -> [(Unique, Period)]
-timersToFire t periods = [ (u, p) | (u, p) <- M.toList periods, next p <= t ]
-
-nextFireTime :: Periods -> Maybe Int64
-nextFireTime periods
-    | M.null periods = Nothing
-    | otherwise = Just $ minimum [ next p | p <- M.elems periods ]
-
-fireTimers :: [(Unique, Period)] -> IO [(Unique, TMVar ())]
-fireTimers toFire = atomically $ forM toFire $ \(u, p) -> do
-    doneVar <- newEmptyTMVar
-    putTMVar (tick p) (Tick doneVar)
-    return (u, doneVar)
-
-waitForTimers :: TVar Bool -> [(Unique, TMVar ())] -> IO [Unique]
-waitForTimers timeoutVar fired = atomically $ do
-    timeoutOver <- readTVar timeoutVar
-    dones <- forM fired $ \(u, doneVar) -> do
-        done <- isJust <$> tryReadTMVar doneVar
-        return (u, done)
-    guard $ timeoutOver || all snd dones
-    return [u | (u, False) <- dones]
-
--- | Handle slow timers (drop and signal them to stop coalescing).
-unCoalesceTimers :: [Unique] -> IO ()
-unCoalesceTimers timers = atomically $ do
-    periods <- fromJust <$> readTVar periodsVar
-    periods' <- foldrM unCoalesceTimer periods timers
-    writeTVar periodsVar $ Just periods'
-
-unCoalesceTimer :: Unique -> Periods -> STM Periods
-unCoalesceTimer u periods = do
-    unCoalesceTimer' (periods M.! u)
-    return $ u `M.delete` periods
-
-unCoalesceTimer' :: Period -> STM ()
-unCoalesceTimer' p = do
-    _ <- tryTakeTMVar (tick p)
-    putTMVar (tick p) UnCoalesce
-
-delayUntilNextFire :: IO ()
-delayUntilNextFire = do
-    Just periods <- readTVarIO periodsVar
-    let tMaybeNext = nextFireTime periods
-    tNow <- now
-    delayVar <- case tMaybeNext of
-        Just tNext -> do
-            -- Work around the Int max bound: threadDelay takes an Int, we can
-            -- only sleep for so long, which is okay, we'll just check timers
-            -- sooner and sleep again.
-            let maxDelay = (maxBound :: Int) `div` 100000
-                delay = (tNext - tNow) `min` fromIntegral maxDelay
-                delayUsec = fromIntegral delay * 100000
-            registerDelay delayUsec
-        Nothing -> newTVarIO False
-    atomically $ do
-        delayOver <- readTVar delayVar
-        periods' <- fromJust <$> readTVar periodsVar
-        let tMaybeNext' = nextFireTime periods'
-        -- Return also if a new period is added (it may fire sooner).
-        guard $ delayOver || tMaybeNext /= tMaybeNext'
diff --git a/src/Xmobar/Config/Parse.hs b/src/Xmobar/Config/Parse.hs
--- a/src/Xmobar/Config/Parse.hs
+++ b/src/Xmobar/Config/Parse.hs
@@ -2,7 +2,7 @@
 ------------------------------------------------------------------------------
 -- |
 -- Module: Xmobar.Config.Parse
--- Copyright: (c) 2018, 2020 Jose Antonio Ortega Ruiz
+-- Copyright: (c) 2018, 2020, 2022 Jose Antonio Ortega Ruiz
 -- License: BSD3-style (see LICENSE)
 --
 -- Maintainer: jao@gnu.org
@@ -61,8 +61,9 @@
 
       perms = permute $ Config
               <$?> pFont <|?> pFontList <|?> pWmClass <|?> pWmName
-              <|?> pBgColor <|?> pFgColor
-              <|?> pPosition <|?> pTextOffset <|?> pTextOffsets
+              <|?> pBgColor <|?> pFgColor <|?> pPosition
+              <|?> pTextOutput <|?> pTextOutputFormat
+              <|?> pTextOffset <|?> pTextOffsets
               <|?> pIconOffset <|?> pBorder
               <|?> pBdColor <|?> pBdWidth <|?> pAlpha <|?> pHideOnStart
               <|?> pAllDesktops <|?> pOverrideRedirect <|?> pPickBroadest
@@ -70,15 +71,18 @@
               <|?> pCommands <|?> pSepChar <|?> pAlignSep <|?> pTemplate
               <|?> pVerbose <|?> pSignal
 
-      fields    = [ "font", "additionalFonts","bgColor", "fgColor"
+      fields    = [ "font", "additionalFonts", "bgColor", "fgColor"
                   , "wmClass", "wmName", "sepChar"
                   , "alignSep" , "border", "borderColor" ,"template"
                   , "position" , "textOffset", "textOffsets", "iconOffset"
                   , "allDesktops", "overrideRedirect", "pickBroadest"
                   , "hideOnStart", "lowerOnStart", "persistent", "iconRoot"
-                  , "alpha", "commands", "verbose", "signal"
+                  , "alpha", "commands", "verbose", "signal", "textOutput"
+                  , "textOutputFormat"
                   ]
 
+      pTextOutput = readField textOutput "textOutput"
+      pTextOutputFormat = readField textOutputFormat "textOutputFormat"
       pFont = strField font "font"
       pFontList = strListField additionalFonts "additionalFonts"
       pWmClass = strField wmClass "wmClass"
diff --git a/src/Xmobar/Config/Types.hs b/src/Xmobar/Config/Types.hs
--- a/src/Xmobar/Config/Types.hs
+++ b/src/Xmobar/Config/Types.hs
@@ -16,7 +16,7 @@
     ( -- * Configuration
       -- $config
       Config (..)
-    , XPosition (..), Align (..), Border(..)
+    , XPosition (..), Align (..), Border (..), TextOutputFormat (..)
     , SignalChan (..)
     ) where
 
@@ -36,6 +36,8 @@
            , bgColor :: String      -- ^ Backgroud color
            , fgColor :: String      -- ^ Default font color
            , position :: XPosition  -- ^ Top Bottom or Static
+           , textOutput :: Bool     -- ^ Write data to stdout instead of X
+           , textOutputFormat :: TextOutputFormat -- ^ Which color format to use for stdout: Ansi or Pango
            , textOffset :: Int      -- ^ Offset from top of window for text
            , textOffsets :: [Int]   -- ^ List of offsets for additionalFonts
            , iconOffset :: Int      -- ^ Offset from top of window for icons
@@ -95,6 +97,8 @@
             | BottomBM Int
             | FullBM Int
               deriving ( Read, Show, Eq )
+
+data TextOutputFormat = Plain | Ansi | Pango | Swaybar deriving (Read, Show, Eq)
 
 newtype SignalChan = SignalChan { unSignalChan :: Maybe (STM.TMVar SignalType) }
 
diff --git a/src/Xmobar/Plugins/Command.hs b/src/Xmobar/Plugins/Command.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Command.hs
@@ -0,0 +1,54 @@
+------------------------------------------------------------------------------
+-- |
+-- Module: Xmobar.Plugins.Command
+-- Copyright: (c) 2018, 2022 Jose Antonio Ortega Ruiz
+-- License: BSD3-style (see LICENSE)
+--
+-- Maintainer: jao@gnu.org
+-- Stability: unstable
+-- Portability: portable
+-- Created: Sun Dec 02, 2018 05:29
+--
+--
+-- The basic Command plugin
+--
+------------------------------------------------------------------------------
+
+
+module Xmobar.Plugins.Command where
+
+import Control.Exception (handle, SomeException(..))
+import System.Process
+import System.Exit
+import System.IO (hClose, hGetLine)
+
+import Xmobar.Run.Exec
+
+data Command = Com Program Args Alias Rate
+             | ComX Program Args String Alias Rate
+               deriving (Show,Read,Eq)
+
+type Args    = [String]
+type Program = String
+type Alias   = String
+type Rate    = Int
+
+instance Exec Command where
+    alias (ComX p _ _ a _) =
+      if p /= "" then (if a == "" then p else a) else ""
+    alias (Com p a al r) = alias (ComX p a "" al r)
+    start (Com p as al r) cb =
+      start (ComX p as ("Could not execute command " ++ p) al r) cb
+    start (ComX prog args msg _ r) cb = if r > 0 then go else exec
+        where go = doEveryTenthSeconds r exec
+              exec = do
+                (i,o,e,p) <- runInteractiveProcess prog args Nothing Nothing
+                exit <- waitForProcess p
+                let closeHandles = hClose o >> hClose i >> hClose e
+                    getL = handle (\(SomeException _) -> return "")
+                                  (hGetLine o)
+                case exit of
+                  ExitSuccess -> do str <- getL
+                                    closeHandles
+                                    cb str
+                  _ -> closeHandles >> cb msg
diff --git a/src/Xmobar/Plugins/Monitors/Cpu.hs b/src/Xmobar/Plugins/Monitors/Cpu.hs
--- a/src/Xmobar/Plugins/Monitors/Cpu.hs
+++ b/src/Xmobar/Plugins/Monitors/Cpu.hs
@@ -4,7 +4,7 @@
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Plugins.Monitors.Cpu
--- Copyright   :  (c) 2011, 2017 Jose Antonio Ortega Ruiz
+-- Copyright   :  (c) 2011, 2017, 2022 Jose Antonio Ortega Ruiz
 --                (c) 2007-2010 Andrea Rossato
 -- License     :  BSD-style (see LICENSE)
 --
@@ -30,7 +30,7 @@
 import Xmobar.Plugins.Monitors.Common
 import Data.IORef (newIORef)
 import System.Console.GetOpt
-import Xmobar.App.Timer (doEveryTenthSeconds)
+import Xmobar.Run.Timer (doEveryTenthSeconds)
 import Control.Monad (void)
 import Xmobar.Plugins.Monitors.Cpu.Common (CpuData(..))
 
diff --git a/src/Xmobar/Plugins/StdinReader.hs b/src/Xmobar/Plugins/StdinReader.hs
--- a/src/Xmobar/Plugins/StdinReader.hs
+++ b/src/Xmobar/Plugins/StdinReader.hs
@@ -26,7 +26,7 @@
 import System.IO
 import System.IO.Error (isEOFError)
 import Xmobar.Run.Exec
-import Xmobar.X11.Actions (stripActions)
+import Xmobar.Run.Actions (stripActions)
 import Control.Exception
 import Control.Monad (forever)
 
diff --git a/src/Xmobar/Plugins/XMonadLog.hs b/src/Xmobar/Plugins/XMonadLog.hs
--- a/src/Xmobar/Plugins/XMonadLog.hs
+++ b/src/Xmobar/Plugins/XMonadLog.hs
@@ -21,14 +21,17 @@
 import Graphics.X11
 import Graphics.X11.Xlib.Extras
 import Xmobar.Run.Exec
+import Xmobar.Run.Actions (stripActions)
+
 #ifdef UTF8
 #undef UTF8
 import Codec.Binary.UTF8.String as UTF8
 #define UTF8
 #endif
+
 import Foreign.C (CChar)
+import Data.List (intercalate)
 import Xmobar.X11.Events (nextEvent')
-import Xmobar.X11.Actions (stripActions)
 
 data XMonadLog = XMonadLog
                | UnsafeXMonadLog
@@ -54,11 +57,12 @@
                 UnsafeXPropertyLog a -> a
                 NamedXPropertyLog a _ -> a
                 UnsafeNamedXPropertyLog a _ -> a
+            stripNL = intercalate " - " . lines
             sanitize = case x of
                 UnsafeXMonadLog -> id
                 UnsafeXPropertyLog _ -> id
                 UnsafeNamedXPropertyLog _ _ -> id
-                _ -> stripActions
+                _ -> stripActions . stripNL
 
         d <- openDisplay ""
         xlog <- internAtom d atom False
diff --git a/src/Xmobar/Run/Actions.hs b/src/Xmobar/Run/Actions.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Run/Actions.hs
@@ -0,0 +1,43 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Xmobar.Run.Actions
+-- Copyright   :  (c) Alexander Polakov
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Run.Actions ( Button
+                          , Action(..)
+                          , runAction
+                          , runAction'
+                          , stripActions) where
+
+import System.Process (system)
+import Control.Monad (void)
+import Text.Regex (Regex, subRegex, mkRegex, matchRegex)
+import Data.Word (Word32)
+
+type Button = Word32
+
+data Action = Spawn [Button] String deriving (Eq, Read, Show)
+
+runAction :: Action -> IO ()
+runAction (Spawn _ s) = void $ system (s ++ "&")
+
+-- | Run action with stdout redirected to stderr
+runAction' :: Action -> IO ()
+runAction' (Spawn _ s) = void $ system (s ++ " 1>&2 &")
+
+stripActions :: String -> String
+stripActions s = case matchRegex actionRegex s of
+  Nothing -> s
+  Just _  -> stripActions strippedOneLevel
+  where
+      strippedOneLevel = subRegex actionRegex s "[action=\\1\\2]\\3[/action]"
+
+actionRegex :: Regex
+actionRegex = mkRegex "<action=`?([^>`]*)`?( +button=[12345]+)?>(.+)</action>"
diff --git a/src/Xmobar/Run/Command.hs b/src/Xmobar/Run/Command.hs
deleted file mode 100644
--- a/src/Xmobar/Run/Command.hs
+++ /dev/null
@@ -1,54 +0,0 @@
-------------------------------------------------------------------------------
--- |
--- Module: Xmobar.Plugins.Command
--- Copyright: (c) 2018 Jose Antonio Ortega Ruiz
--- License: BSD3-style (see LICENSE)
---
--- Maintainer: jao@gnu.org
--- Stability: unstable
--- Portability: portable
--- Created: Sun Dec 02, 2018 05:29
---
---
--- The basic Command plugin
---
-------------------------------------------------------------------------------
-
-
-module Xmobar.Run.Command where
-
-import Control.Exception (handle, SomeException(..))
-import System.Process
-import System.Exit
-import System.IO (hClose, hGetLine)
-
-import Xmobar.Run.Exec
-
-data Command = Com Program Args Alias Rate
-             | ComX Program Args String Alias Rate
-               deriving (Show,Read,Eq)
-
-type Args    = [String]
-type Program = String
-type Alias   = String
-type Rate    = Int
-
-instance Exec Command where
-    alias (ComX p _ _ a _) =
-      if p /= "" then (if a == "" then p else a) else ""
-    alias (Com p a al r) = alias (ComX p a "" al r)
-    start (Com p as al r) cb =
-      start (ComX p as ("Could not execute command " ++ p) al r) cb
-    start (ComX prog args msg _ r) cb = if r > 0 then go else exec
-        where go = doEveryTenthSeconds r exec
-              exec = do
-                (i,o,e,p) <- runInteractiveProcess prog args Nothing Nothing
-                exit <- waitForProcess p
-                let closeHandles = hClose o >> hClose i >> hClose e
-                    getL = handle (\(SomeException _) -> return "")
-                                  (hGetLine o)
-                case exit of
-                  ExitSuccess -> do str <- getL
-                                    closeHandles
-                                    cb str
-                  _ -> closeHandles >> cb msg
diff --git a/src/Xmobar/Run/Exec.hs b/src/Xmobar/Run/Exec.hs
--- a/src/Xmobar/Run/Exec.hs
+++ b/src/Xmobar/Run/Exec.hs
@@ -22,7 +22,7 @@
 import Prelude
 import Data.Char
 
-import Xmobar.App.Timer (doEveryTenthSeconds, tenthSeconds)
+import Xmobar.Run.Timer (doEveryTenthSeconds, tenthSeconds)
 import Xmobar.System.Signal
 
 class Show e => Exec e where
diff --git a/src/Xmobar/Run/Loop.hs b/src/Xmobar/Run/Loop.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Run/Loop.hs
@@ -0,0 +1,126 @@
+{-# LANGUAGE CPP #-}
+
+------------------------------------------------------------------------------
+-- |
+-- Module: Xmobar.Run.Loop
+-- Copyright: (c) 2022 Jose Antonio Ortega Ruiz
+-- License: BSD3-style (see LICENSE)
+--
+-- Maintainer: jao@gnu.org
+-- Stability: unstable
+-- Portability: unportable
+-- Created: Fri Jan 28, 2022 03:20
+--
+--
+-- Running a thread for each defined Command in a loop
+--
+------------------------------------------------------------------------------
+
+module Xmobar.Run.Loop (LoopFunction, loop) where
+
+import Control.Concurrent (forkIO)
+import Control.Exception (bracket_, bracket, handle, SomeException(..))
+import Control.Concurrent.STM
+import Control.Concurrent.Async (Async, async, cancel)
+import Control.Monad (guard, void, unless)
+import Data.Maybe (isJust)
+import Data.Foldable (for_)
+
+import Xmobar.System.Signal
+import Xmobar.Config.Types
+import Xmobar.Run.Runnable (Runnable)
+import Xmobar.Run.Exec (start, trigger, alias)
+import Xmobar.Run.Template
+import Xmobar.Run.Timer (withTimer)
+
+#ifdef DBUS
+import Xmobar.System.DBus
+#endif
+
+newRefreshLock :: IO (TMVar ())
+newRefreshLock = newTMVarIO ()
+
+refreshLock :: TMVar () -> IO a -> IO a
+refreshLock var = bracket_ lock unlock
+    where
+        lock = atomically $ takeTMVar var
+        unlock = atomically $ putTMVar var ()
+
+refreshLockT :: TMVar () -> STM a -> STM a
+refreshLockT var action = do
+    takeTMVar var
+    r <- action
+    putTMVar var ()
+    return r
+
+type LoopFunction = TMVar SignalType -> TVar [String] -> IO ()
+
+loop :: Config -> LoopFunction -> IO ()
+loop conf looper = withDeferSignals $ do
+  cls <- mapM (parseTemplate (commands conf) (sepChar conf))
+                (splitTemplate (alignSep conf) (template conf))
+  let confSig = unSignalChan (signal conf)
+  sig <- maybe newEmptyTMVarIO pure confSig
+  unless (isJust confSig) $ setupSignalHandler sig
+  refLock <- newRefreshLock
+  withTimer (refreshLock refLock) $
+    bracket (mapM (mapM $ startCommand sig) cls)
+            cleanupThreads
+            $ \vars -> do
+      tv <- initLoop sig refLock vars
+      looper sig tv
+
+cleanupThreads :: [[([Async ()], a)]] -> IO ()
+cleanupThreads vars =
+  for_ (concat vars) $ \(asyncs, _) ->
+    for_ asyncs cancel
+
+-- | Initialises context for an event loop, returning a TVar that
+-- will hold the current list of values computed by commands.
+initLoop :: TMVar SignalType -> TMVar () -> [[([Async ()], TVar String)]]
+         -> IO (TVar [String])
+initLoop sig lock vs = do
+  tv <- newTVarIO ([] :: [String])
+  _ <- forkIO (handle (handler "checker") (checker tv [] vs sig lock))
+#ifdef DBUS
+  runIPC sig
+#endif
+  return tv
+  where
+    handler thing (SomeException e) =
+      void $ putStrLn ("Thread " ++ thing ++ " failed: " ++ show e)
+
+-- | Runs a command as an independent thread and returns its Async handles
+-- and the TVar the command will be writing to.
+startCommand :: TMVar SignalType
+             -> (Runnable,String,String)
+             -> IO ([Async ()], TVar String)
+startCommand sig (com,s,ss)
+    | alias com == "" = do var <- newTVarIO is
+                           atomically $ writeTVar var (s ++ ss)
+                           return ([], var)
+    | otherwise = do var <- newTVarIO is
+                     let cb str = atomically $ writeTVar var (s ++ str ++ ss)
+                     a1 <- async $ start com cb
+                     a2 <- async $ trigger com $ maybe (return ())
+                                                 (atomically . putTMVar sig)
+                     return ([a1, a2], var)
+    where is = s ++ "Updating..." ++ ss
+
+-- | Send signal to eventLoop every time a var is updated
+checker :: TVar [String]
+           -> [String]
+           -> [[([Async ()], TVar String)]]
+           -> TMVar SignalType
+           -> TMVar ()
+           -> IO ()
+checker tvar ov vs sig pauser = do
+      nval <- atomically $ refreshLockT pauser $ do
+              nv <- mapM concatV vs
+              guard (nv /= ov)
+              writeTVar tvar nv
+              return nv
+      atomically $ putTMVar sig Wakeup
+      checker tvar nval vs sig pauser
+    where
+      concatV = fmap concat . mapM (readTVar . snd)
diff --git a/src/Xmobar/Run/Parsers.hs b/src/Xmobar/Run/Parsers.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Run/Parsers.hs
@@ -0,0 +1,234 @@
+{-# LANGUAGE FlexibleContexts #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Xmobar.Run.Parsers
+-- Copyright   :  (c) Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  portable
+--
+-- Parsing for template substrings
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Run.Parsers ( parseString
+                          , colorComponents
+                          , Segment
+                          , FontIndex
+                          , Box(..)
+                          , BoxBorder(..)
+                          , BoxOffset(..)
+                          , BoxMargins(..)
+                          , TextRenderInfo(..)
+                          , Widget(..)) where
+
+import Control.Monad (guard, mzero)
+import Data.Maybe (fromMaybe)
+import Data.Int (Int32)
+import Text.ParserCombinators.Parsec
+import Text.Read (readMaybe)
+import Foreign.C.Types (CInt)
+
+import Xmobar.Config.Types
+import Xmobar.Run.Actions
+
+data Widget = Icon String | Text String | Hspace Int32 deriving Show
+
+data BoxOffset = BoxOffset Align Int32 deriving (Eq, Show)
+-- margins: Top, Right, Bottom, Left
+data BoxMargins = BoxMargins Int32 Int32 Int32 Int32 deriving (Eq, Show)
+data BoxBorder = BBTop
+               | BBBottom
+               | BBVBoth
+               | BBLeft
+               | BBRight
+               | BBHBoth
+               | BBFull
+                 deriving ( Read, Eq, Show )
+data Box = Box BoxBorder BoxOffset CInt String BoxMargins deriving (Eq, Show)
+data TextRenderInfo =
+    TextRenderInfo { tColorsString   :: String
+                   , tBgTopOffset    :: Int32
+                   , tBgBottomOffset :: Int32
+                   , tBoxes          :: [Box]
+                   } deriving Show
+type FontIndex   = Int
+
+type Segment = (Widget, TextRenderInfo, FontIndex, Maybe [Action])
+
+-- | Runs the string parser
+parseString :: Config -> String -> IO [Segment]
+parseString c s =
+    case parse (stringParser ci 0 Nothing) "" s of
+      Left  _ -> return [(Text $ "Could not parse string: " ++ s
+                          , ci
+                          , 0
+                          , Nothing)]
+      Right x -> return (concat x)
+    where ci = TextRenderInfo (fgColor c) 0 0 []
+
+-- | Splits a colors string into its two components
+colorComponents :: Config -> String -> (String, String)
+colorComponents conf c =
+  case break (==',') c of
+    (f,',':b) -> (f, b)
+    (f,    _) -> (f, bgColor conf)
+
+allParsers :: TextRenderInfo
+           -> FontIndex
+           -> Maybe [Action]
+           -> Parser [Segment]
+allParsers c f a =  textParser c f a
+                <|> try (iconParser c f a)
+                <|> try (hspaceParser c f a)
+                <|> try (rawParser c f a)
+                <|> try (actionParser c f a)
+                <|> try (fontParser c a)
+                <|> try (boxParser c f a)
+                <|> colorParser c f a
+
+-- | Gets the string and combines the needed parsers
+stringParser :: TextRenderInfo -> FontIndex -> Maybe [Action] -> Parser [[Segment]]
+stringParser c f a = manyTill (allParsers c f a) eof
+
+-- | Parses a maximal string without markup.
+textParser :: TextRenderInfo -> FontIndex -> Maybe [Action] -> Parser [Segment]
+textParser c f a = do s <- many1 $
+                            noneOf "<" <|>
+                              try (notFollowedBy' (char '<')
+                                    (try (string "fc=")  <|>
+                                     try (string "box")  <|>
+                                     try (string "fn=")  <|>
+                                     try (string "action=") <|>
+                                     try (string "/action>") <|>
+                                     try (string "icon=") <|>
+                                     try (string "hspace=") <|>
+                                     try (string "raw=") <|>
+                                     try (string "/fn>") <|>
+                                     try (string "/box>") <|>
+                                     string "/fc>"))
+                      return [(Text s, c, f, a)]
+
+-- | Parse a "raw" tag, which we use to prevent other tags from creeping in.
+-- The format here is net-string-esque: a literal "<raw=" followed by a
+-- string of digits (base 10) denoting the length of the raw string,
+-- a literal ":" as digit-string-terminator, the raw string itself, and
+-- then a literal "/>".
+rawParser :: TextRenderInfo
+          -> FontIndex
+          -> Maybe [Action]
+          -> Parser [Segment]
+rawParser c f a = do
+  string "<raw="
+  lenstr <- many1 digit
+  char ':'
+  case reads lenstr of
+    [(len,[])] -> do
+      guard ((len :: Integer) <= fromIntegral (maxBound :: Int))
+      s <- count (fromIntegral len) anyChar
+      string "/>"
+      return [(Text s, c, f, a)]
+    _ -> mzero
+
+-- | Wrapper for notFollowedBy that returns the result of the first parser.
+--   Also works around the issue that, at least in Parsec 3.0.0, notFollowedBy
+--   accepts only parsers with return type Char.
+notFollowedBy' :: Parser a -> Parser b -> Parser a
+notFollowedBy' p e = do x <- p
+                        notFollowedBy $ try (e >> return '*')
+                        return x
+
+iconParser :: TextRenderInfo -> FontIndex -> Maybe [Action] -> Parser [Segment]
+iconParser c f a = do
+  string "<icon="
+  i <- manyTill (noneOf ">") (try (string "/>"))
+  return [(Icon i, c, f, a)]
+
+hspaceParser :: TextRenderInfo -> FontIndex -> Maybe [Action] -> Parser [Segment]
+hspaceParser c f a = do
+  string "<hspace="
+  pVal <- manyTill digit (try (string "/>"))
+  return [(Hspace (fromMaybe 0 $ readMaybe pVal), c, f, a)]
+
+actionParser :: TextRenderInfo -> FontIndex -> Maybe [Action] -> Parser [Segment]
+actionParser c f act = do
+  string "<action="
+  command <- choice [between (char '`') (char '`') (many1 (noneOf "`")),
+                   many1 (noneOf ">")]
+  buttons <- (char '>' >> return "1") <|> (space >> spaces >>
+    between (string "button=") (string ">") (many1 (oneOf "12345")))
+  let a = Spawn (toButtons buttons) command
+      a' = case act of
+        Nothing -> Just [a]
+        Just act' -> Just $ a : act'
+  s <- manyTill (allParsers c f a') (try $ string "</action>")
+  return (concat s)
+
+toButtons :: String -> [Button]
+toButtons = map (\x -> read [x])
+
+-- | Parsers a string wrapped in a color specification.
+colorParser :: TextRenderInfo -> FontIndex -> Maybe [Action] -> Parser [Segment]
+colorParser (TextRenderInfo _ _ _ bs) f a = do
+  c <- between (string "<fc=") (string ">") colors
+  let colorParts = break (==':') c
+  let (ot,ob) = case break (==',') (Prelude.drop 1 $ snd colorParts) of
+             (top,',':btm) -> (top, btm)
+             (top,      _) -> (top, top)
+  s <- manyTill
+       (allParsers (TextRenderInfo (fst colorParts) (fromMaybe (-1) $ readMaybe ot) (fromMaybe (-1) $ readMaybe ob) bs) f a)
+       (try $ string "</fc>")
+  return (concat s)
+
+-- | Parses a string wrapped in a box specification.
+boxParser :: TextRenderInfo -> FontIndex -> Maybe [Action] -> Parser [Segment]
+boxParser (TextRenderInfo cs ot ob bs) f a = do
+  c <- between (string "<box") (string ">") (option "" (many1 (alphaNum <|> char '=' <|> char ' ' <|> char '#' <|> char ',')))
+  let b = Box BBFull (BoxOffset C 0) 1 cs (BoxMargins 0 0 0 0)
+  let g = boxReader b (words c)
+  s <- manyTill
+       (allParsers (TextRenderInfo cs ot ob (g : bs)) f a)
+       (try $ string "</box>")
+  return (concat s)
+
+boxReader :: Box -> [String] -> Box
+boxReader b [] = b
+boxReader b (x:xs) = do
+  let (param,val) = case break (=='=') x of
+                 (p,'=':v) -> (p, v)
+                 (p,    _) -> (p, "")
+  boxReader (boxParamReader b param val) xs
+
+boxParamReader :: Box -> String -> String -> Box
+boxParamReader b _ "" = b
+boxParamReader (Box bb off lw fc mgs) "type" val =
+  Box (fromMaybe bb $ readMaybe ("BB" ++ val)) off lw fc mgs
+boxParamReader (Box bb (BoxOffset alg off) lw fc mgs) "offset" (a:o) =
+  Box bb (BoxOffset (fromMaybe alg $ readMaybe [a]) (fromMaybe off $ readMaybe o)) lw fc mgs
+boxParamReader (Box bb off lw fc mgs) "width" val =
+  Box bb off (fromMaybe lw $ readMaybe val) fc mgs
+boxParamReader (Box bb off lw _ mgs) "color" val =
+  Box bb off lw val mgs
+boxParamReader (Box bb off lw fc mgs@(BoxMargins mt mr mb ml)) ('m':pos) val = do
+  let mgs' = case pos of
+         "t" -> BoxMargins (fromMaybe mt $ readMaybe val) mr mb ml
+         "r" -> BoxMargins mt (fromMaybe mr $ readMaybe val) mb ml
+         "b" -> BoxMargins mt mr (fromMaybe mb $ readMaybe val) ml
+         "l" -> BoxMargins mt mr mb (fromMaybe ml $ readMaybe val)
+         _ -> mgs
+  Box bb off lw fc mgs'
+boxParamReader b _ _ = b
+
+-- | Parsers a string wrapped in a font specification.
+fontParser :: TextRenderInfo -> Maybe [Action] -> Parser [Segment]
+fontParser c a = do
+  f <- between (string "<fn=") (string ">") colors
+  s <- manyTill (allParsers c (fromMaybe 0 $ readMaybe f) a) (try $ string "</fn>")
+  return (concat s)
+
+-- | Parses a color specification (hex or named)
+colors :: Parser String
+colors = many1 (alphaNum <|> char ',' <|> char ':' <|> char '#')
diff --git a/src/Xmobar/Run/Template.hs b/src/Xmobar/Run/Template.hs
--- a/src/Xmobar/Run/Template.hs
+++ b/src/Xmobar/Run/Template.hs
@@ -20,8 +20,9 @@
 import qualified Data.Map as Map
 import Text.ParserCombinators.Parsec
 
+import Xmobar.Plugins.Command
+
 import Xmobar.Run.Exec
-import Xmobar.Run.Command
 import Xmobar.Run.Runnable
 
 defaultAlign :: String
diff --git a/src/Xmobar/Run/Timer.hs b/src/Xmobar/Run/Timer.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Run/Timer.hs
@@ -0,0 +1,221 @@
+{-# LANGUAGE LambdaCase #-}
+------------------------------------------------------------------------------
+-- |
+-- Module: Xmobar.Run.Timer
+-- Copyright: (c) 2019, 2020, 2022 Tomáš Janoušek
+-- License: BSD3-style (see LICENSE)
+--
+-- Maintainer: Tomáš Janoušek <tomi@nomi.cz>
+-- Stability: unstable
+--
+-- Timer coalescing for recurring actions.
+--
+------------------------------------------------------------------------------
+
+module Xmobar.Run.Timer
+    ( doEveryTenthSeconds
+    , tenthSeconds
+    , withTimer
+    ) where
+
+import Control.Concurrent (threadDelay)
+import Control.Concurrent.Async (withAsync)
+import Control.Concurrent.STM
+import Control.Exception
+import Control.Monad (forever, forM, guard)
+import Data.Foldable (foldrM, for_)
+import Data.Int (Int64)
+import Data.Map (Map)
+import qualified Data.Map as M
+import Data.Maybe (isJust, fromJust)
+import Data.Time.Clock.POSIX (getPOSIXTime)
+import Data.Unique
+import System.IO.Unsafe (unsafePerformIO)
+
+type Periods = Map Unique Period
+
+data Tick = Tick (TMVar ()) | UnCoalesce
+
+data Period = Period { rate :: Int64, next :: Int64, tick :: TMVar Tick }
+
+data UnCoalesceException = UnCoalesceException deriving Show
+instance Exception UnCoalesceException
+
+{-# NOINLINE periodsVar #-}
+periodsVar :: TVar (Maybe Periods)
+periodsVar = unsafePerformIO $ newTVarIO Nothing
+
+now :: IO Int64
+now = do
+    posix <- getPOSIXTime
+    return $ floor (10 * posix)
+
+newPeriod :: Int64 -> IO (Unique, Period)
+newPeriod r = do
+    u <- newUnique
+    t <- now
+    v <- newEmptyTMVarIO
+    let t' = t - t `mod` r
+    return (u, Period { rate = r, next = t', tick = v })
+
+-- | Perform a given action every N tenths of a second.
+--
+-- The timer is aligned (coalesced) with other timers to minimize the number
+-- of wakeups and unnecessary redraws. If the action takes too long (one
+-- second or when the next timer is due), coalescing is disabled for it and it
+-- falls back to periodic sleep.
+doEveryTenthSeconds :: Int -> IO () -> IO ()
+doEveryTenthSeconds r action =
+    doEveryTenthSecondsCoalesced r action `catch` \UnCoalesceException ->
+        doEveryTenthSecondsSleeping r action
+
+-- | Perform a given action every N tenths of a second,
+-- coalesce with other timers using a given Timer instance.
+doEveryTenthSecondsCoalesced :: Int -> IO () -> IO ()
+doEveryTenthSecondsCoalesced r action = do
+    (u, p) <- newPeriod (fromIntegral r)
+    bracket_ (push u p) (pop u) $ forever $ bracket (wait p) done $ const action
+    where
+        push u p = atomically $ modifyTVar' periodsVar $ \case
+            Just periods -> Just $ M.insert u p periods
+            Nothing -> throw UnCoalesceException
+        pop u = atomically $ modifyTVar' periodsVar $ \case
+            Just periods -> Just $ M.delete u periods
+            Nothing -> Nothing
+
+        wait p = atomically (takeTMVar $ tick p) >>= \case
+            Tick doneVar -> return doneVar
+            UnCoalesce -> throwIO UnCoalesceException
+        done doneVar = atomically $ putTMVar doneVar ()
+
+-- | Perform a given action every N tenths of a second,
+-- making no attempt to synchronize with other timers.
+doEveryTenthSecondsSleeping :: Int -> IO () -> IO ()
+doEveryTenthSecondsSleeping r action = go
+    where go = action >> tenthSeconds r >> go
+
+-- | Sleep for a given amount of tenths of a second.
+--
+-- (Work around the Int max bound: since threadDelay takes an Int, it
+-- is not possible to set a thread delay grater than about 45 minutes.
+-- With a little recursion we solve the problem.)
+tenthSeconds :: Int -> IO ()
+tenthSeconds s | s >= x = do threadDelay (x * 100000)
+                             tenthSeconds (s - x)
+               | otherwise = threadDelay (s * 100000)
+               where x = (maxBound :: Int) `div` 100000
+
+-- | Start the timer coordination thread and perform a given IO action (this
+-- is meant to surround the entire xmobar execution), terminating the timer
+-- thread afterwards.
+--
+-- Additionally, if the timer thread fails, individual
+-- 'doEveryTenthSecondsCoalesced' invocations that are waiting to be
+-- coordinated by it are notified to fall back to periodic sleeping.
+--
+-- The timer thread _will_ fail immediately when running in a non-threaded
+-- RTS.
+withTimer :: (IO () -> IO ()) -> IO a -> IO a
+withTimer pauseRefresh action =
+    withAsync (timerThread `finally` cleanup) $ const action
+    where
+        timerThread = do
+            atomically $ writeTVar periodsVar $ Just M.empty
+            timerLoop pauseRefresh
+
+        cleanup = atomically $ readTVar periodsVar >>= \case
+            Just periods -> do
+                for_ periods unCoalesceTimer'
+                writeTVar periodsVar Nothing
+            Nothing -> return ()
+
+timerLoop :: (IO () -> IO ()) -> IO ()
+timerLoop pauseRefresh = forever $ do
+    tNow <- now
+    (toFire, tMaybeNext) <- atomically $ do
+        periods <- fromJust <$> readTVar periodsVar
+        let toFire = timersToFire tNow periods
+        let periods' = advanceTimers tNow periods
+        let tMaybeNext = nextFireTime periods'
+        writeTVar periodsVar $ Just periods'
+        return (toFire, tMaybeNext)
+    pauseRefresh $ do
+        -- To avoid multiple refreshes, pause refreshing for up to 1 second,
+        -- fire timers and wait for them to finish (update their text).
+        -- Those that need more time (e.g. weather monitors) will be dropped
+        -- from timer coalescing and will fall back to periodic sleep.
+        timeoutVar <- registerDelay $ case tMaybeNext of
+            Just tNext -> fromIntegral ((tNext - tNow) `max` 10) * 100000
+            Nothing -> 1000000
+        fired <- fireTimers toFire
+        timeouted <- waitForTimers timeoutVar fired
+        unCoalesceTimers timeouted
+    delayUntilNextFire
+
+advanceTimers :: Int64 -> Periods -> Periods
+advanceTimers t = M.map advance
+    where
+        advance p | next p <= t = p { next = t - t `mod` rate p + rate p }
+                  | otherwise = p
+
+timersToFire :: Int64 -> Periods -> [(Unique, Period)]
+timersToFire t periods = [ (u, p) | (u, p) <- M.toList periods, next p <= t ]
+
+nextFireTime :: Periods -> Maybe Int64
+nextFireTime periods
+    | M.null periods = Nothing
+    | otherwise = Just $ minimum [ next p | p <- M.elems periods ]
+
+fireTimers :: [(Unique, Period)] -> IO [(Unique, TMVar ())]
+fireTimers toFire = atomically $ forM toFire $ \(u, p) -> do
+    doneVar <- newEmptyTMVar
+    putTMVar (tick p) (Tick doneVar)
+    return (u, doneVar)
+
+waitForTimers :: TVar Bool -> [(Unique, TMVar ())] -> IO [Unique]
+waitForTimers timeoutVar fired = atomically $ do
+    timeoutOver <- readTVar timeoutVar
+    dones <- forM fired $ \(u, doneVar) -> do
+        done <- isJust <$> tryReadTMVar doneVar
+        return (u, done)
+    guard $ timeoutOver || all snd dones
+    return [u | (u, False) <- dones]
+
+-- | Handle slow timers (drop and signal them to stop coalescing).
+unCoalesceTimers :: [Unique] -> IO ()
+unCoalesceTimers timers = atomically $ do
+    periods <- fromJust <$> readTVar periodsVar
+    periods' <- foldrM unCoalesceTimer periods timers
+    writeTVar periodsVar $ Just periods'
+
+unCoalesceTimer :: Unique -> Periods -> STM Periods
+unCoalesceTimer u periods = do
+    unCoalesceTimer' (periods M.! u)
+    return $ u `M.delete` periods
+
+unCoalesceTimer' :: Period -> STM ()
+unCoalesceTimer' p = do
+    _ <- tryTakeTMVar (tick p)
+    putTMVar (tick p) UnCoalesce
+
+delayUntilNextFire :: IO ()
+delayUntilNextFire = do
+    Just periods <- readTVarIO periodsVar
+    let tMaybeNext = nextFireTime periods
+    tNow <- now
+    delayVar <- case tMaybeNext of
+        Just tNext -> do
+            -- Work around the Int max bound: threadDelay takes an Int, we can
+            -- only sleep for so long, which is okay, we'll just check timers
+            -- sooner and sleep again.
+            let maxDelay = (maxBound :: Int) `div` 100000
+                delay = (tNext - tNow) `min` fromIntegral maxDelay
+                delayUsec = fromIntegral delay * 100000
+            registerDelay delayUsec
+        Nothing -> newTVarIO False
+    atomically $ do
+        delayOver <- readTVar delayVar
+        periods' <- fromJust <$> readTVar periodsVar
+        let tMaybeNext' = nextFireTime periods'
+        -- Return also if a new period is added (it may fire sooner).
+        guard $ delayOver || tMaybeNext /= tMaybeNext'
diff --git a/src/Xmobar/Run/Types.hs b/src/Xmobar/Run/Types.hs
--- a/src/Xmobar/Run/Types.hs
+++ b/src/Xmobar/Run/Types.hs
@@ -2,7 +2,7 @@
 ------------------------------------------------------------------------------
 -- |
 -- Module: Xmobar.Run.Types
--- Copyright: (c) 2018 Jose Antonio Ortega Ruiz
+-- Copyright: (c) 2018, 2022 Jose Antonio Ortega Ruiz
 -- License: BSD3-style (see LICENSE)
 --
 -- Maintainer: jao@gnu.org
@@ -19,6 +19,7 @@
 module Xmobar.Run.Types(runnableTypes) where
 
 import {-# SOURCE #-} Xmobar.Run.Runnable()
+import Xmobar.Plugins.Command
 import Xmobar.Plugins.Monitors
 import Xmobar.Plugins.Date
 import Xmobar.Plugins.PipeReader
@@ -44,8 +45,6 @@
 #ifdef KRAKEN
 import Xmobar.Plugins.Kraken
 #endif
-
-import Xmobar.Run.Command
 
 -- | An alias for tuple types that is more convenient for long lists.
 type a :*: b = (a, b)
diff --git a/src/Xmobar/System/Utils.hs b/src/Xmobar/System/Utils.hs
--- a/src/Xmobar/System/Utils.hs
+++ b/src/Xmobar/System/Utils.hs
@@ -3,7 +3,7 @@
 ------------------------------------------------------------------------------
 -- |
 -- Module: Utils
--- Copyright: (c) 2010, 2018, 2020 Jose Antonio Ortega Ruiz
+-- Copyright: (c) 2010, 2018, 2020, 2022 Jose Antonio Ortega Ruiz
 -- License: BSD3-style (see LICENSE)
 --
 -- Maintainer: Jose A Ortega Ruiz <jao@gnu.org>
@@ -21,10 +21,19 @@
   ( expandHome
   , changeLoop
   , safeIndex
+  , forkThread
   ) where
 
 import Control.Monad
 import Control.Concurrent.STM
+import Control.Exception (handle, SomeException(..))
+
+#ifdef THREADED_RUNTIME
+import Control.Concurrent (forkOS)
+#else
+import Control.Concurrent (forkIO)
+#endif
+
 import qualified Data.List.NonEmpty as NE
 import Data.Maybe (fromMaybe)
 
@@ -34,6 +43,18 @@
 expandHome :: FilePath -> IO FilePath
 expandHome ('~':'/':path) = fmap (</> path) (getEnv "HOME")
 expandHome p = return p
+
+forkThread :: String -> IO () -> IO ()
+forkThread name action = do
+#ifdef THREADED_RUNTIME
+    _ <- forkOS (handle (onError name) action)
+#else
+    _ <- forkIO (handle (onError name) action)
+#endif
+    return ()
+  where
+    onError thing (SomeException e) =
+      void $ putStrLn ("Thread " ++ thing ++ " failed: " ++ show e)
 
 changeLoop :: Eq a => STM a -> (a -> IO ()) -> IO ()
 changeLoop s f = atomically s >>= go
diff --git a/src/Xmobar/Text/Ansi.hs b/src/Xmobar/Text/Ansi.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Text/Ansi.hs
@@ -0,0 +1,49 @@
+------------------------------------------------------------------------------
+-- |
+-- Module: Xmobar.Text.Ansi
+-- Copyright: (c) 2022 Jose Antonio Ortega Ruiz
+-- License: BSD3-style (see LICENSE)
+--
+-- Maintainer: jao@gnu.org
+-- Stability: unstable
+-- Portability: portable
+-- Created: Fri Feb 4, 2022 01:10
+--
+--
+-- Codification with ANSI (color) escape codes
+--
+------------------------------------------------------------------------------
+
+module Xmobar.Text.Ansi (withAnsiColor) where
+
+import Data.List (intercalate)
+import Data.Char (toLower)
+
+asInt :: String -> String
+asInt x = case (reads $ "0x" ++ x)  :: [(Integer, String)] of
+  [(v, "") ] -> show v
+  _ -> ""
+
+namedColor :: String -> String
+namedColor c =
+  case map toLower c of
+    "black" -> "0"; "red" -> "1"; "green" -> "2"; "yellow" -> "3"; "blue" -> "4";
+    "magenta" -> "5"; "cyan" -> "6"; "white" -> "7";
+    "brightblack" -> "8"; "brightred" -> "9"; "brightgreen" -> "10";
+    "brightyellow" -> "11"; "brightblue" -> "12";
+    "brightmagenta" -> "13"; "brightcyan" -> "14"; "brightwhite" -> "15";
+    _ -> ""
+
+ansiCode :: String -> String
+ansiCode ('#':r:g:[b]) = ansiCode ['#', '0', r, '0', g, '0', b]
+ansiCode ('#':r0:r1:g0:g1:b0:[b1]) =
+  "2;" ++ intercalate ";" (map asInt [[r0,r1], [g0,g1], [b0,b1]])
+ansiCode ('#':n) = ansiCode n
+ansiCode c = "5;" ++ if null i then namedColor c else i where i = asInt c
+
+withAnsiColor :: (String, String) -> String -> String
+withAnsiColor (fg, bg) s = wrap "38;" fg (wrap "48;" bg s)
+  where wrap cd cl w =
+          if null cl
+          then w
+          else "\x1b[" ++ cd ++ ansiCode cl ++ "m" ++ w ++ "\x1b[0m"
diff --git a/src/Xmobar/Text/Loop.hs b/src/Xmobar/Text/Loop.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Text/Loop.hs
@@ -0,0 +1,48 @@
+------------------------------------------------------------------------------
+-- |
+-- Module: Xmobar.Text.Loop
+-- Copyright: (c) 2022 Jose Antonio Ortega Ruiz
+-- License: BSD3-style (see LICENSE)
+--
+-- Maintainer: jao@gnu.org
+-- Stability: unstable
+-- Portability: unportable
+-- Created: Fri Jan 28, 2022 01:21
+--
+--
+-- Text-only event loop
+--
+------------------------------------------------------------------------------
+
+module Xmobar.Text.Loop (textLoop) where
+
+import Prelude hiding (lookup)
+import System.IO (hSetBuffering, stdin, stdout, BufferMode(LineBuffering))
+
+import Control.Concurrent.STM
+
+import Xmobar.System.Signal
+import Xmobar.Config.Types (Config)
+import Xmobar.Run.Loop (loop)
+import Xmobar.Text.Output (initLoop, format)
+
+-- | Starts the main event loop and threads
+textLoop :: Config -> IO ()
+textLoop conf = do
+  hSetBuffering stdin LineBuffering
+  hSetBuffering stdout LineBuffering
+  initLoop conf
+  loop conf (eventLoop conf)
+
+-- | Continuously wait for a signal from a thread or a interrupt handler
+eventLoop :: Config -> TMVar SignalType -> TVar [String] -> IO ()
+eventLoop cfg signal tv = do
+  typ <- atomically $ takeTMVar signal
+  case typ of
+    Wakeup -> updateString cfg tv >>= putStrLn >> eventLoop cfg signal tv
+    _ -> eventLoop cfg signal tv
+
+updateString :: Config -> TVar [String] -> IO String
+updateString conf v = do
+  s <- readTVarIO v
+  format conf (concat s)
diff --git a/src/Xmobar/Text/Output.hs b/src/Xmobar/Text/Output.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Text/Output.hs
@@ -0,0 +1,55 @@
+-- |
+-- Module: Xmobar.Text.Output
+-- Copyright: (c) 2022 Jose Antonio Ortega Ruiz
+-- License: BSD3-style (see LICENSE)
+--
+-- Maintainer: jao@gnu.org
+-- Stability: unstable
+-- Portability: portable
+-- Created: Fri Feb 4, 2022 01:10
+--
+--
+-- Format strings emitted by Commands into output strings
+--
+------------------------------------------------------------------------------
+
+module Xmobar.Text.Output (initLoop, format) where
+
+import Xmobar.Config.Types (Config(textOutputFormat, additionalFonts, font)
+                           , TextOutputFormat(..))
+import Xmobar.Run.Parsers ( Segment
+                          , Widget(..)
+                          , parseString
+                          , tColorsString
+                          , colorComponents)
+
+import Xmobar.Text.Ansi (withAnsiColor)
+import Xmobar.Text.Pango (withPangoMarkup)
+import Xmobar.Text.Swaybar (formatSwaybar, prepare)
+
+initLoop :: Config -> IO ()
+initLoop conf = case textOutputFormat conf of
+  Swaybar -> prepare
+  _ -> return ()
+
+formatWithColor :: Config -> Segment -> String
+formatWithColor conf (Text s, info, idx, _) =
+  case textOutputFormat conf of
+    Ansi -> withAnsiColor (fg, bg) s
+    Pango -> withPangoMarkup fg bg fn s
+    _ -> s
+  where (fg, bg) = colorComponents conf (tColorsString info)
+        fonts = additionalFonts conf
+        fn = if idx < 1 || idx > length fonts
+             then font conf
+             else fonts !! (idx - 1)
+formatWithColor conf (Hspace n, i, x, y) =
+   formatWithColor conf (Text $ replicate (fromIntegral n) ' ', i, x, y)
+formatWithColor _ _ = ""
+
+format :: Config -> String -> IO String
+format conf s = do
+  segments <- parseString conf s
+  case textOutputFormat conf of
+    Swaybar -> return $ formatSwaybar conf segments
+    _ -> return (concatMap (formatWithColor conf) segments)
diff --git a/src/Xmobar/Text/Pango.hs b/src/Xmobar/Text/Pango.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Text/Pango.hs
@@ -0,0 +1,48 @@
+------------------------------------------------------------------------------
+-- |
+-- Module: Xmobar.Text.Pango
+-- Copyright: (c) 2022 Jose Antonio Ortega Ruiz
+-- License: BSD3-style (see LICENSE)
+--
+-- Author: Pavel Kalugin
+-- Maintainer: jao@gnu.org
+-- Stability: unstable
+-- Portability: portable
+-- Created: Fri Feb 4, 2022 01:15
+--
+--
+-- Codification with Pango markup
+--
+------------------------------------------------------------------------------
+
+module Xmobar.Text.Pango (withPangoColor, withPangoFont, withPangoMarkup) where
+
+import Text.Printf (printf)
+import Data.List (isPrefixOf)
+
+replaceAll :: (Eq a) => a -> [a] -> [a] -> [a]
+replaceAll c s = concatMap (\x -> if x == c then s else [x])
+
+xmlEscape :: String -> String
+xmlEscape s = replaceAll '"' "&quot;" $
+              replaceAll '\'' "&apos;" $
+              replaceAll '<' "&lt;" $
+              replaceAll '>' "&gt;" $
+              replaceAll '&' "&amp;" s
+
+withPangoColor :: (String, String) -> String -> String
+withPangoColor (fg, bg) s =
+  printf fmt (xmlEscape fg) (xmlEscape bg) (xmlEscape s)
+  where fmt = "<span foreground=\"%s\" background=\"%s\">%s</span>"
+
+fixXft :: String -> String
+fixXft font = if "xft:" `isPrefixOf` font then drop 4 font else font
+
+withPangoFont :: String -> String -> String
+withPangoFont font txt = printf fmt (fixXft font) (xmlEscape txt)
+  where fmt = "<span font=\"%s\">%s</span>"
+
+withPangoMarkup :: String -> String -> String -> String -> String
+withPangoMarkup fg bg font txt =
+  printf fmt (fixXft font) (xmlEscape fg) (xmlEscape bg) (xmlEscape txt)
+  where fmt = "<span font=\"%s\" foreground=\"%s\" background=\"%s\">%s</span>"
diff --git a/src/Xmobar/Text/Swaybar.hs b/src/Xmobar/Text/Swaybar.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Text/Swaybar.hs
@@ -0,0 +1,147 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+------------------------------------------------------------------------------
+-- |
+-- Module: Xmobar.Text.Swaybar
+-- Copyright: (c) 2022 Jose Antonio Ortega Ruiz
+-- License: BSD3-style (see LICENSE)
+--
+-- Maintainer: jao@gnu.org
+-- Stability: unstable
+-- Portability: portable
+-- Created: Fri Feb 4, 2022 03:58
+--
+--
+-- Segment codification using swaybar-protocol JSON strings
+--
+------------------------------------------------------------------------------
+
+module Xmobar.Text.Swaybar (prepare, formatSwaybar) where
+
+import Data.Aeson
+
+import Data.ByteString.Lazy.UTF8 (toString)
+
+import GHC.Generics
+
+import Xmobar.Config.Types (Config (additionalFonts))
+
+import Xmobar.Run.Parsers ( Segment
+                          , Widget(..)
+                          , Box(..)
+                          , BoxBorder(..)
+                          , FontIndex
+                          , tBoxes
+                          , tColorsString
+                          , colorComponents)
+
+import Xmobar.Text.SwaybarClicks (startHandler)
+import Xmobar.Text.Pango (withPangoFont)
+
+data Preamble =
+  Preamble {version :: !Int, click_events :: Bool} deriving (Eq,Show,Generic)
+
+asString :: ToJSON a => a -> String
+asString = toString . encode
+
+preamble :: String
+preamble = (asString $ Preamble { version = 1, click_events = True }) ++ "\x0A["
+
+data Block =
+  Block { full_text :: !String
+        , name :: !String
+        , color :: Maybe String
+        , background :: Maybe String
+        , separator :: !Bool
+        , separator_block_width :: !Int
+        , border :: Maybe String
+        , border_top :: Maybe Int
+        , border_bottom :: Maybe Int
+        , border_left :: Maybe Int
+        , border_right :: Maybe Int
+        , markup :: Maybe String
+        } deriving (Eq,Show,Generic)
+
+
+defaultBlock :: Block
+defaultBlock = Block { full_text = ""
+                     , name = ""
+                     , color = Nothing
+                     , background = Nothing
+                     , separator = False
+                     , separator_block_width = 0
+                     , border = Nothing
+                     , border_top = Nothing
+                     , border_bottom = Nothing
+                     , border_left = Nothing
+                     , border_right = Nothing
+                     , markup = Nothing
+                     }
+
+instance ToJSON Block where
+  toJSON = genericToJSON defaultOptions
+    { omitNothingFields = True }
+
+instance ToJSON Preamble
+
+withBox :: Box -> Block -> Block
+withBox (Box b _ n c _) block =
+  (case b of
+     BBFull -> bl { border_right = w, border_left = w
+                  , border_bottom = w, border_top = w  }
+     BBTop -> bl { border_top = w }
+     BBBottom -> bl { border_bottom = w }
+     BBVBoth -> bl { border_bottom = w, border_top = w }
+     BBLeft -> bl { border_left = w }
+     BBRight -> bl { border_right = w }
+     BBHBoth -> bl { border_right = w, border_left = w }
+  ) { border = bc }
+  where w = Just (fromIntegral n)
+        bc = if null c then Nothing else Just c
+        j0 = Just 0
+        bl = block { border_right = j0, border_left = j0
+                   , border_bottom = j0, border_top = j0  }
+
+withFont :: Config -> FontIndex -> Block -> Block
+withFont conf idx block =
+  if idx < 1 || idx > length fonts then block
+  else block { markup = Just $ fonts !! (idx - 1) }
+  where fonts = additionalFonts conf
+
+withPango :: Block -> Block
+withPango block = case markup block of
+  Nothing -> block
+  Just fnt -> block { full_text = txt fnt, markup = Just "pango"}
+  where txt fn = withPangoFont fn (full_text block)
+
+formatSwaybar' :: Config -> Segment -> Block
+formatSwaybar' conf (Text txt, info, idx, as) =
+  foldr withBox (withFont conf idx block) (tBoxes info)
+  where (fg, bg) = colorComponents conf (tColorsString info)
+        block = defaultBlock { full_text = txt
+                             , color = Just fg
+                             , background = Just bg
+                             , name = show as
+                             }
+formatSwaybar' conf (Hspace n, info, i, a) =
+  formatSwaybar' conf (Text (replicate (fromIntegral n) ' '), info, i, a)
+formatSwaybar' _ _ = defaultBlock
+
+collectBlock :: Block -> [Block] -> [Block]
+collectBlock b [] = [b]
+collectBlock b (h:bs) =
+  if b {full_text = ""} == h {full_text = ""} then
+    h {full_text = full_text b ++ full_text h} : bs
+  else b:h:bs
+
+collectSegment :: Config -> Segment -> [Block] -> [Block]
+collectSegment config segment blocks =
+  if null $ full_text b then blocks else collectBlock b blocks
+  where b = formatSwaybar' config segment
+
+formatSwaybar :: Config -> [Segment] -> String
+formatSwaybar conf segs = asString (map withPango blocks) ++ ","
+  where blocks = foldr (collectSegment conf) [] segs
+
+prepare :: IO ()
+prepare = startHandler >> putStrLn preamble
diff --git a/src/Xmobar/Text/SwaybarClicks.hs b/src/Xmobar/Text/SwaybarClicks.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Text/SwaybarClicks.hs
@@ -0,0 +1,60 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+------------------------------------------------------------------------------
+-- |
+-- Module: Xmobar.Text.SwaybarClicks
+-- Copyright: (c) 2022 Jose Antonio Ortega Ruiz
+-- License: BSD3-style (see LICENSE)
+--
+-- Maintainer: jao@gnu.org
+-- Stability: unstable
+-- Portability: portable
+-- Created: Fri Feb 4, 2022 03:58
+--
+--
+-- Handling of "click" events sent by swaybar via stdin
+--
+------------------------------------------------------------------------------
+
+
+module Xmobar.Text.SwaybarClicks (startHandler) where
+
+import Control.Monad (when)
+
+
+import Data.Aeson
+
+-- import qualified Data.ByteString.Lazy as BL
+
+import GHC.Generics
+
+import Xmobar.System.Utils (forkThread)
+import Xmobar.Run.Actions (Action (..), runAction')
+
+import Data.ByteString.Lazy.UTF8 (fromString)
+
+data Click =
+  Click { name :: String , button :: Int } deriving (Eq,Show,Generic)
+
+instance FromJSON Click
+
+runClickAction :: Int -> Action -> IO ()
+runClickAction b a@(Spawn bs _) =
+  when (fromIntegral b `elem` bs) (runAction' a)
+
+handleClick :: Maybe Click -> IO ()
+handleClick Nothing = return ()
+handleClick (Just click) = do
+  let mas = read (name click) :: Maybe [Action]
+      b = button click
+  maybe (return ()) (mapM_ (runClickAction b)) mas
+
+toClick :: String -> Maybe Click
+toClick (',':s) = toClick s
+toClick s = decode (fromString s)
+
+readClicks :: IO ()
+readClicks = getLine >>= handleClick . toClick >> readClicks
+
+startHandler :: IO ()
+startHandler = forkThread "Swaybar event handler" readClicks
diff --git a/src/Xmobar/X11/Actions.hs b/src/Xmobar/X11/Actions.hs
deleted file mode 100644
--- a/src/Xmobar/X11/Actions.hs
+++ /dev/null
@@ -1,34 +0,0 @@
------------------------------------------------------------------------------
--- |
--- Module      :  Xmobar.Actions
--- Copyright   :  (c) Alexander Polakov
--- License     :  BSD-style (see LICENSE)
---
--- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
--- Stability   :  unstable
--- Portability :  unportable
---
------------------------------------------------------------------------------
-
-module Xmobar.X11.Actions (Action(..), runAction, stripActions) where
-
-import System.Process (system)
-import Control.Monad (void)
-import Text.Regex (Regex, subRegex, mkRegex, matchRegex)
-import Graphics.X11.Types (Button)
-
-data Action = Spawn [Button] String
-                deriving (Eq, Show)
-
-runAction :: Action -> IO ()
-runAction (Spawn _ s) = void $ system (s ++ "&")
-
-stripActions :: String -> String
-stripActions s = case matchRegex actionRegex s of
-  Nothing -> s
-  Just _  -> stripActions strippedOneLevel
-  where
-      strippedOneLevel = subRegex actionRegex s "[action=\\1\\2]\\3[/action]"
-
-actionRegex :: Regex
-actionRegex = mkRegex "<action=`?([^>`]*)`?( +button=[12345]+)?>(.+)</action>"
diff --git a/src/Xmobar/X11/Bitmap.hs b/src/Xmobar/X11/Bitmap.hs
--- a/src/Xmobar/X11/Bitmap.hs
+++ b/src/Xmobar/X11/Bitmap.hs
@@ -2,7 +2,7 @@
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  X11.Bitmap
--- Copyright   :  (C) 2013, 2015, 2017, 2018 Alexander Polakov
+-- Copyright   :  (C) 2013, 2015, 2017, 2018, 2022 Alexander Polakov
 -- License     :  BSD3
 --
 -- Maintainer  :  jao@gnu.org
@@ -23,9 +23,9 @@
 import System.Directory (doesFileExist)
 import System.FilePath ((</>))
 import System.Mem.Weak ( addFinalizer )
+import Xmobar.Run.Actions (Action)
+import Xmobar.Run.Parsers (TextRenderInfo(..), Widget(..))
 import Xmobar.X11.ColorCache
-import Xmobar.X11.Parsers (TextRenderInfo(..), Widget(..))
-import Xmobar.X11.Actions (Action)
 
 #ifdef XPM
 import Xmobar.X11.XPMFile(readXPMFile)
diff --git a/src/Xmobar/X11/Draw.hs b/src/Xmobar/X11/Draw.hs
--- a/src/Xmobar/X11/Draw.hs
+++ b/src/Xmobar/X11/Draw.hs
@@ -4,7 +4,7 @@
 ------------------------------------------------------------------------------
 -- |
 -- Module: Xmobar.X11.Draw
--- Copyright: (c) 2018, 2020 Jose Antonio Ortega Ruiz
+-- Copyright: (c) 2018, 2020, 2022 Jose Antonio Ortega Ruiz
 -- License: BSD3-style (see LICENSE)
 --
 -- Maintainer: jao@gnu.org
@@ -28,17 +28,16 @@
 import Data.List ((\\))
 import qualified Data.List.NonEmpty as NE
 
-import Graphics.X11.Xlib hiding (textExtents, textWidth)
+import Graphics.X11.Xlib hiding (textExtents, textWidth, Segment)
 import Graphics.X11.Xlib.Extras
 
 import Xmobar.Config.Types
+import Xmobar.Run.Parsers hiding (parseString)
 import qualified Xmobar.X11.Bitmap as B
-import Xmobar.X11.Actions (Action(..))
 import Xmobar.X11.Types
 import Xmobar.X11.Text
 import Xmobar.X11.ColorCache
 import Xmobar.X11.Window (drawBorder)
-import Xmobar.X11.Parsers hiding (parseString)
 import Xmobar.System.Utils (safeIndex)
 
 #ifdef XFT
@@ -50,7 +49,7 @@
 fi = fromIntegral
 
 -- | Draws in and updates the window
-drawInWin :: Rectangle -> [[(Widget, TextRenderInfo, Int, Maybe [Action])]] -> X ()
+drawInWin :: Rectangle -> [[Segment]] -> X ()
 drawInWin wr@(Rectangle _ _ wid ht) ~[left,center,right] = do
   r <- ask
   let (c,d) = (config &&& display) r
@@ -71,6 +70,7 @@
 #endif
   withColors d [bgColor c, borderColor c] $ \[bgcolor, bdcolor] -> do
     gc <- liftIO $ createGC  d w
+    liftIO $ setGraphicsExposures d gc False
 #if XFT
     when (alpha c == 255) $ do
 #else
@@ -89,8 +89,8 @@
     -- free up everything (we do not want to leak memory!)
     liftIO $ freeGC d gc
     liftIO $ freePixmap d p
-    -- resync
-    liftIO $ sync d False
+    -- resync (discard events, we don't read/process events from this display conn)
+    liftIO $ sync d True
 
 verticalOffset :: (Integral b, Integral a, MonadIO m) =>
                   a -> Widget -> XFont -> Int -> Config -> m b
@@ -105,8 +105,19 @@
   | otherwise = return $ fi (ht `div` 2) - 1
 verticalOffset _ (Hspace _) _ voffs _ = return $ fi voffs
 
-printString :: Display -> Drawable -> XFont -> GC -> String -> String
-            -> Position -> Position -> Position -> Position -> String -> Int -> IO ()
+printString :: Display
+            -> Drawable
+            -> XFont
+            -> GC
+            -> String
+            -> String
+            -> Position
+            -> Position
+            -> Position
+            -> Position
+            -> String
+            -> Int
+            -> IO ()
 printString d p (Core fs) gc fc bc x y _ _ s a = do
     setFont d gc $ fontFromFontStruct fs
     withColors d [fc, bc] $ \[fc', bc'] -> do
@@ -133,8 +144,14 @@
 #endif
 
 -- | An easy way to print the stuff we need to print
-printStrings :: Drawable -> GC -> NE.NonEmpty XFont -> NE.NonEmpty Int -> Position
-             -> Align -> [((Position, Position), Box)] -> [(Widget, TextRenderInfo, Int, Position)] -> X ()
+printStrings :: Drawable
+             -> GC
+             -> NE.NonEmpty XFont
+             -> NE.NonEmpty Int
+             -> Position
+             -> Align
+             -> [((Position, Position), Box)]
+             -> [(Widget, TextRenderInfo, Int, Position)] -> X ()
 printStrings _ _ _ _ _ _ _ [] = return ()
 printStrings dr gc fontlist voffs offs a boxes sl@((s,c,i,l):xs) = do
   r <- ask
@@ -149,9 +166,7 @@
                  C -> (remWidth + offs) `div` 2
                  R -> remWidth
                  L -> offs
-      (fc,bc) = case break (==',') (tColorsString c) of
-                 (f,',':b) -> (f, b           )
-                 (f,    _) -> (f, bgColor conf)
+      (fc,bc) = colorComponents conf (tColorsString c)
   valign <- verticalOffset ht s fontst voff conf
   let (ht',ay) = case (tBgTopOffset c, tBgBottomOffset c) of
                    (-1,_)  -> (0, -1)
@@ -165,14 +180,20 @@
     (Hspace _) -> liftIO $ return ()
   let triBoxes = tBoxes c
       dropBoxes = filter (\(_,b) -> b `notElem` triBoxes) boxes
-      boxes' = map (\((x1,_),b) -> ((x1, offset + l), b)) (filter (\(_,b) -> b `elem` triBoxes) boxes)
+      boxes' = map (\((x1,_),b) -> ((x1, offset + l), b))
+                   (filter (\(_,b) -> b `elem` triBoxes) boxes)
             ++ map ((offset, offset + l),) (triBoxes \\ map snd boxes)
   if Prelude.null xs
     then liftIO $ drawBoxes d dr gc (fromIntegral ht) (dropBoxes ++ boxes')
     else liftIO $ drawBoxes d dr gc (fromIntegral ht) dropBoxes
   printStrings dr gc fontlist voffs (offs + l) a boxes' xs
 
-drawBoxes :: Display -> Drawable -> GC -> Position -> [((Position, Position), Box)] -> IO ()
+drawBoxes :: Display
+          -> Drawable
+          -> GC
+          -> Position
+          -> [((Position, Position), Box)]
+          -> IO ()
 drawBoxes _ _ _ _ [] = return ()
 drawBoxes d dr gc ht (b:bs) = do
   let (xx, Box bb offset lineWidth fc mgs) = b
@@ -195,9 +216,18 @@
       _ -> drawBoxBorder d dr gc bb    offset ht xx lw mgs
   drawBoxes d dr gc ht bs
 
-drawBoxBorder :: Display -> Drawable -> GC -> BoxBorder -> BoxOffset -> Position
-                 -> (Position, Position) -> Position -> BoxMargins -> IO ()
-drawBoxBorder d dr gc pos (BoxOffset alg offset) ht (x1,x2) lw (BoxMargins mt mr mb ml) = do
+drawBoxBorder :: Display
+              -> Drawable
+              -> GC
+              -> BoxBorder
+              -> BoxOffset
+              -> Position
+              -> (Position, Position)
+              -> Position
+              -> BoxMargins
+              -> IO ()
+drawBoxBorder
+  d dr gc pos (BoxOffset alg offset) ht (x1,x2) lw (BoxMargins mt mr mb ml) = do
   let (p1,p2) = case alg of
                  L -> (0,      -offset)
                  C -> (offset, -offset)
diff --git a/src/Xmobar/X11/Loop.hs b/src/Xmobar/X11/Loop.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/X11/Loop.hs
@@ -0,0 +1,229 @@
+{-# LANGUAGE CPP #-}
+
+------------------------------------------------------------------------------
+-- |
+-- Module: Xmobar.App.X11EventLoop
+-- Copyright: (c) 2018, 2020, 2022 Jose Antonio Ortega Ruiz
+-- License: BSD3-style (see LICENSE)
+--
+-- Maintainer: jao@gnu.org
+-- Stability: unstable
+-- Portability: portable
+-- Created: Sat Nov 24, 2018 19:40
+--
+--
+-- Event loop
+--
+------------------------------------------------------------------------------
+
+module Xmobar.X11.Loop (x11Loop) where
+
+import Prelude hiding (lookup)
+import Graphics.X11.Xlib hiding (textExtents, textWidth, Segment)
+import Graphics.X11.Xlib.Extras
+import Graphics.X11.Xinerama
+import Graphics.X11.Xrandr
+
+import Control.Arrow ((&&&))
+import Control.Monad.Reader
+import Control.Concurrent
+import Control.Concurrent.STM
+
+import Data.Bits
+import Data.Map hiding (foldr, map, filter)
+import qualified Data.Map as Map
+import Data.List.NonEmpty (NonEmpty(..))
+
+import Data.Maybe (fromJust, isJust)
+import qualified Data.List.NonEmpty as NE
+
+import Xmobar.System.Signal
+import Xmobar.Config.Types ( persistent
+                           , font
+                           , additionalFonts
+                           , textOffset
+                           , textOffsets
+                           , position
+                           , iconRoot
+                           , Config
+                           , Align(..)
+                           , XPosition(..))
+
+import Xmobar.Run.Actions
+import Xmobar.Run.Parsers
+import Xmobar.X11.Window
+import Xmobar.X11.Text
+import Xmobar.X11.Draw
+import Xmobar.X11.Bitmap as Bitmap
+import Xmobar.X11.Types
+import Xmobar.System.Utils (safeIndex, forkThread)
+
+import Xmobar.Run.Loop (loop)
+
+#ifndef THREADED_RUNTIME
+import Xmobar.X11.Events(nextEvent')
+#endif
+
+#ifdef XFT
+import Graphics.X11.Xft
+#endif
+
+runX :: XConf -> X () -> IO ()
+runX xc f = runReaderT f xc
+
+-- | Starts the main event loop and threads
+x11Loop :: Config -> IO ()
+x11Loop conf = do
+  initThreads
+  d <- openDisplay ""
+  fs <- initFont d (font conf)
+  fl <- mapM (initFont d) (additionalFonts conf)
+  let ic = Map.empty
+      to = textOffset conf
+      ts = textOffsets conf ++ replicate (length fl) (-1)
+#ifdef XFT
+  xftInitFtLibrary
+#endif
+  (r,w) <- createWin d fs conf
+  loop conf (startLoop (XConf d r w (fs :| fl) (to :| ts) ic conf))
+
+startLoop :: XConf -> TMVar SignalType -> TVar [String] -> IO ()
+startLoop xcfg@(XConf _ _ w _ _ _ _) sig tv = do
+    forkThread "X event handler" (x11EventLoop w sig)
+    signalLoop xcfg [] sig tv
+
+-- | Translates X11 events received by w to signals handled by signalLoop
+x11EventLoop :: Window -> TMVar SignalType -> IO ()
+x11EventLoop w signal =
+  allocaXEvent $ \e -> do
+    dpy <- openDisplay ""
+    xrrSelectInput dpy (defaultRootWindow dpy) rrScreenChangeNotifyMask
+    selectInput dpy w (exposureMask .|. structureNotifyMask .|. buttonPressMask)
+
+    forever $ do
+#ifdef THREADED_RUNTIME
+      nextEvent dpy e
+#else
+      nextEvent' dpy e
+#endif
+      ev <- getEvent e
+      case ev of
+        ConfigureEvent {} -> atomically $ putTMVar signal Reposition
+        ExposeEvent {} -> atomically $ putTMVar signal Wakeup
+        RRScreenChangeNotifyEvent {} -> atomically $ putTMVar signal Reposition
+        ButtonEvent {} -> atomically $
+               putTMVar signal (Action (ev_button ev) (fi $ ev_x ev))
+        _ -> return ()
+
+-- | Continuously wait for a signal from a thread or an interrupt handler
+signalLoop :: XConf
+          -> [([Action], Position, Position)]
+          -> TMVar SignalType
+          -> TVar [String]
+          -> IO ()
+signalLoop xc@(XConf d r w fs vos is cfg) as signal tv = do
+      typ <- atomically $ takeTMVar signal
+      case typ of
+         Wakeup -> do
+            str <- updateString cfg tv
+            xc' <- updateCache d w is (iconRoot cfg) str >>=
+                     \c -> return xc { iconS = c }
+            as' <- updateActions xc r str
+            runX xc' $ drawInWin r str
+            signalLoop xc' as' signal tv
+
+         Reposition ->
+            reposWindow cfg
+
+         ChangeScreen -> do
+            ncfg <- updateConfigPosition cfg
+            reposWindow ncfg
+
+         Hide   t -> hide   (t*100*1000)
+         Reveal t -> reveal (t*100*1000)
+         Toggle t -> toggle t
+
+         TogglePersistent -> signalLoop
+            xc { config = cfg { persistent = not $ persistent cfg } } as signal tv
+
+         Action but x -> action but x
+
+    where
+        isPersistent = not $ persistent cfg
+        loopOn = signalLoop xc as signal tv
+        hide t
+            | t == 0 =
+                when isPersistent (hideWindow d w) >> loopOn
+            | otherwise = do
+                void $ forkIO
+                     $ threadDelay t >> atomically (putTMVar signal $ Hide 0)
+                loopOn
+
+        reveal t
+            | t == 0 = do
+                when isPersistent (showWindow r cfg d w)
+                loopOn
+            | otherwise = do
+                void $ forkIO
+                     $ threadDelay t >> atomically (putTMVar signal $ Reveal 0)
+                loopOn
+
+        toggle t = do
+            ismapped <- isMapped d w
+            atomically (putTMVar signal $ if ismapped then Hide t else Reveal t)
+            loopOn
+
+        reposWindow rcfg = do
+          r' <- repositionWin d w (NE.head fs) rcfg
+          signalLoop (XConf d r' w fs vos is rcfg) as signal tv
+
+        updateConfigPosition ocfg =
+          case position ocfg of
+            OnScreen n o -> do
+              srs <- getScreenInfo d
+              return (if n == length srs
+                       then
+                        (ocfg {position = OnScreen 1 o})
+                       else
+                        (ocfg {position = OnScreen (n+1) o}))
+            o -> return (ocfg {position = OnScreen 1 o})
+
+        action button x = do
+          mapM_ runAction $
+            filter (\(Spawn b _) -> button `elem` b) $
+            concatMap (\(a,_,_) -> a) $
+            filter (\(_, from, to) -> x >= from && x <= to) as
+          loopOn
+
+updateString :: Config -> TVar [String] -> IO [[Segment]]
+updateString conf v = do
+  s <- readTVarIO v
+  let l:c:r:_ = s ++ repeat ""
+  liftIO $ mapM (parseString conf) [l, c, r]
+
+updateActions :: XConf -> Rectangle -> [[Segment]]
+              -> IO [([Action], Position, Position)]
+updateActions conf (Rectangle _ _ wid _) ~[left,center,right] = do
+  let (d,fs) = (display &&& fontListS) conf
+      strLn :: [Segment] -> IO [(Maybe [Action], Position, Position)]
+      strLn  = liftIO . mapM getCoords
+      iconW i = maybe 0 Bitmap.width (lookup i $ iconS conf)
+      getCoords (Text s,_,i,a) =
+        textWidth d (safeIndex fs i) s >>= \tw -> return (a, 0, fi tw)
+      getCoords (Icon s,_,_,a) = return (a, 0, fi $ iconW s)
+      getCoords (Hspace w,_,_,a) = return (a, 0, fi w)
+      partCoord off xs = map (\(a, x, x') -> (fromJust a, x, x')) $
+                         filter (\(a, _,_) -> isJust a) $
+                         scanl (\(_,_,x') (a,_,w') -> (a, x', x' + w'))
+                               (Nothing, 0, off)
+                               xs
+      totSLen = foldr (\(_,_,len) -> (+) len) 0
+      remWidth xs = fi wid - totSLen xs
+      offs = 1
+      offset a xs = case a of
+                     C -> (remWidth xs + offs) `div` 2
+                     R -> remWidth xs
+                     L -> offs
+  fmap concat $ mapM (\(a,xs) ->
+                       (\xs' -> partCoord (offset a xs') xs') <$> strLn xs) $
+                     zip [L,C,R] [left,center,right]
diff --git a/src/Xmobar/X11/Parsers.hs b/src/Xmobar/X11/Parsers.hs
deleted file mode 100644
--- a/src/Xmobar/X11/Parsers.hs
+++ /dev/null
@@ -1,227 +0,0 @@
-{-# LANGUAGE FlexibleContexts #-}
-
------------------------------------------------------------------------------
--- |
--- Module      :  Xmobar.Parsers
--- Copyright   :  (c) Andrea Rossato
--- License     :  BSD-style (see LICENSE)
---
--- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
--- Stability   :  unstable
--- Portability :  unportable
---
--- Parsing for template substrings
---
------------------------------------------------------------------------------
-
-module Xmobar.X11.Parsers (parseString, Box(..), BoxBorder(..), BoxOffset(..),
-  BoxMargins(..), TextRenderInfo(..), Widget(..)) where
-
-import Xmobar.Config.Types
-import Xmobar.X11.Actions
-
-import Control.Monad (guard, mzero)
-import Data.Maybe (fromMaybe)
-import Data.Int (Int32)
-import Text.ParserCombinators.Parsec
-import Text.Read (readMaybe)
-import Graphics.X11.Types (Button)
-import Foreign.C.Types (CInt)
-
-data Widget = Icon String | Text String | Hspace Int32 deriving Show
-
-data BoxOffset = BoxOffset Align Int32 deriving (Eq, Show)
--- margins: Top, Right, Bottom, Left
-data BoxMargins = BoxMargins Int32 Int32 Int32 Int32 deriving (Eq, Show)
-data BoxBorder = BBTop
-               | BBBottom
-               | BBVBoth
-               | BBLeft
-               | BBRight
-               | BBHBoth
-               | BBFull
-                 deriving ( Read, Eq, Show )
-data Box = Box BoxBorder BoxOffset CInt String BoxMargins deriving (Eq, Show)
-data TextRenderInfo =
-    TextRenderInfo { tColorsString   :: String
-                   , tBgTopOffset    :: Int32
-                   , tBgBottomOffset :: Int32
-                   , tBoxes          :: [Box]
-                   } deriving Show
-type FontIndex   = Int
-
--- | Runs the string parser
-parseString :: Config -> String
-               -> IO [(Widget, TextRenderInfo, FontIndex, Maybe [Action])]
-parseString c s =
-    case parse (stringParser ci 0 Nothing) "" s of
-      Left  _ -> return [(Text $ "Could not parse string: " ++ s
-                          , ci
-                          , 0
-                          , Nothing)]
-      Right x -> return (concat x)
-    where ci = TextRenderInfo (fgColor c) 0 0 []
-
-allParsers :: TextRenderInfo
-           -> FontIndex
-           -> Maybe [Action]
-           -> Parser [(Widget, TextRenderInfo, FontIndex, Maybe [Action])]
-allParsers c f a =  textParser c f a
-                <|> try (iconParser c f a)
-                <|> try (hspaceParser c f a)
-                <|> try (rawParser c f a)
-                <|> try (actionParser c f a)
-                <|> try (fontParser c a)
-                <|> try (boxParser c f a)
-                <|> colorParser c f a
-
--- | Gets the string and combines the needed parsers
-stringParser :: TextRenderInfo -> FontIndex -> Maybe [Action]
-                -> Parser [[(Widget, TextRenderInfo, FontIndex, Maybe [Action])]]
-stringParser c f a = manyTill (allParsers c f a) eof
-
--- | Parses a maximal string without markup.
-textParser :: TextRenderInfo -> FontIndex -> Maybe [Action]
-              -> Parser [(Widget, TextRenderInfo, FontIndex, Maybe [Action])]
-textParser c f a = do s <- many1 $
-                            noneOf "<" <|>
-                              try (notFollowedBy' (char '<')
-                                    (try (string "fc=")  <|>
-                                     try (string "box")  <|>
-                                     try (string "fn=")  <|>
-                                     try (string "action=") <|>
-                                     try (string "/action>") <|>
-                                     try (string "icon=") <|>
-                                     try (string "hspace=") <|>
-                                     try (string "raw=") <|>
-                                     try (string "/fn>") <|>
-                                     try (string "/box>") <|>
-                                     string "/fc>"))
-                      return [(Text s, c, f, a)]
-
--- | Parse a "raw" tag, which we use to prevent other tags from creeping in.
--- The format here is net-string-esque: a literal "<raw=" followed by a
--- string of digits (base 10) denoting the length of the raw string,
--- a literal ":" as digit-string-terminator, the raw string itself, and
--- then a literal "/>".
-rawParser :: TextRenderInfo
-          -> FontIndex
-          -> Maybe [Action]
-          -> Parser [(Widget, TextRenderInfo, FontIndex, Maybe [Action])]
-rawParser c f a = do
-  string "<raw="
-  lenstr <- many1 digit
-  char ':'
-  case reads lenstr of
-    [(len,[])] -> do
-      guard ((len :: Integer) <= fromIntegral (maxBound :: Int))
-      s <- count (fromIntegral len) anyChar
-      string "/>"
-      return [(Text s, c, f, a)]
-    _ -> mzero
-
--- | Wrapper for notFollowedBy that returns the result of the first parser.
---   Also works around the issue that, at least in Parsec 3.0.0, notFollowedBy
---   accepts only parsers with return type Char.
-notFollowedBy' :: Parser a -> Parser b -> Parser a
-notFollowedBy' p e = do x <- p
-                        notFollowedBy $ try (e >> return '*')
-                        return x
-
-iconParser :: TextRenderInfo -> FontIndex -> Maybe [Action]
-              -> Parser [(Widget, TextRenderInfo, FontIndex, Maybe [Action])]
-iconParser c f a = do
-  string "<icon="
-  i <- manyTill (noneOf ">") (try (string "/>"))
-  return [(Icon i, c, f, a)]
-
-hspaceParser :: TextRenderInfo -> FontIndex -> Maybe [Action]
-              -> Parser [(Widget, TextRenderInfo, FontIndex, Maybe [Action])]
-hspaceParser c f a = do
-  string "<hspace="
-  pVal <- manyTill digit (try (string "/>"))
-  return [(Hspace (fromMaybe 0 $ readMaybe pVal), c, f, a)]
-
-actionParser :: TextRenderInfo -> FontIndex -> Maybe [Action]
-                -> Parser [(Widget, TextRenderInfo, FontIndex, Maybe [Action])]
-actionParser c f act = do
-  string "<action="
-  command <- choice [between (char '`') (char '`') (many1 (noneOf "`")),
-                   many1 (noneOf ">")]
-  buttons <- (char '>' >> return "1") <|> (space >> spaces >>
-    between (string "button=") (string ">") (many1 (oneOf "12345")))
-  let a = Spawn (toButtons buttons) command
-      a' = case act of
-        Nothing -> Just [a]
-        Just act' -> Just $ a : act'
-  s <- manyTill (allParsers c f a') (try $ string "</action>")
-  return (concat s)
-
-toButtons :: String -> [Button]
-toButtons = map (\x -> read [x])
-
--- | Parsers a string wrapped in a color specification.
-colorParser :: TextRenderInfo -> FontIndex -> Maybe [Action]
-               -> Parser [(Widget, TextRenderInfo, FontIndex, Maybe [Action])]
-colorParser (TextRenderInfo _ _ _ bs) f a = do
-  c <- between (string "<fc=") (string ">") colors
-  let colorParts = break (==':') c
-  let (ot,ob) = case break (==',') (Prelude.drop 1 $ snd colorParts) of
-             (top,',':btm) -> (top, btm)
-             (top,      _) -> (top, top)
-  s <- manyTill
-       (allParsers (TextRenderInfo (fst colorParts) (fromMaybe (-1) $ readMaybe ot) (fromMaybe (-1) $ readMaybe ob) bs) f a)
-       (try $ string "</fc>")
-  return (concat s)
-
--- | Parses a string wrapped in a box specification.
-boxParser :: TextRenderInfo -> FontIndex -> Maybe [Action]
-              -> Parser [(Widget, TextRenderInfo, FontIndex, Maybe [Action])]
-boxParser (TextRenderInfo cs ot ob bs) f a = do
-  c <- between (string "<box") (string ">") (option "" (many1 (alphaNum <|> char '=' <|> char ' ' <|> char '#' <|> char ',')))
-  let b = Box BBFull (BoxOffset C 0) 1 cs (BoxMargins 0 0 0 0)
-  let g = boxReader b (words c)
-  s <- manyTill
-       (allParsers (TextRenderInfo cs ot ob (g : bs)) f a)
-       (try $ string "</box>")
-  return (concat s)
-
-boxReader :: Box -> [String] -> Box
-boxReader b [] = b
-boxReader b (x:xs) = do
-  let (param,val) = case break (=='=') x of
-                 (p,'=':v) -> (p, v)
-                 (p,    _) -> (p, "")
-  boxReader (boxParamReader b param val) xs
- 
-boxParamReader :: Box -> String -> String -> Box
-boxParamReader b _ "" = b
-boxParamReader (Box bb off lw fc mgs) "type" val = 
-  Box (fromMaybe bb $ readMaybe ("BB" ++ val)) off lw fc mgs
-boxParamReader (Box bb (BoxOffset alg off) lw fc mgs) "offset" (a:o) =
-  Box bb (BoxOffset (fromMaybe alg $ readMaybe [a]) (fromMaybe off $ readMaybe o)) lw fc mgs
-boxParamReader (Box bb off lw fc mgs) "width" val = 
-  Box bb off (fromMaybe lw $ readMaybe val) fc mgs
-boxParamReader (Box bb off lw _ mgs) "color" val = 
-  Box bb off lw val mgs
-boxParamReader (Box bb off lw fc mgs@(BoxMargins mt mr mb ml)) ('m':pos) val = do
-  let mgs' = case pos of
-         "t" -> BoxMargins (fromMaybe mt $ readMaybe val) mr mb ml
-         "r" -> BoxMargins mt (fromMaybe mr $ readMaybe val) mb ml
-         "b" -> BoxMargins mt mr (fromMaybe mb $ readMaybe val) ml
-         "l" -> BoxMargins mt mr mb (fromMaybe ml $ readMaybe val)
-         _ -> mgs
-  Box bb off lw fc mgs'
-boxParamReader b _ _ = b
-
--- | Parsers a string wrapped in a font specification.
-fontParser :: TextRenderInfo -> Maybe [Action]
-              -> Parser [(Widget, TextRenderInfo, FontIndex, Maybe [Action])]
-fontParser c a = do
-  f <- between (string "<fn=") (string ">") colors
-  s <- manyTill (allParsers c (fromMaybe 0 $ readMaybe f) a) (try $ string "</fn>")
-  return (concat s)
-
--- | Parses a color specification (hex or named)
-colors :: Parser String
-colors = many1 (alphaNum <|> char ',' <|> char ':' <|> char '#')
diff --git a/src/Xmobar/X11/Window.hs b/src/Xmobar/X11/Window.hs
--- a/src/Xmobar/X11/Window.hs
+++ b/src/Xmobar/X11/Window.hs
@@ -84,7 +84,7 @@
     TopW a i -> Rectangle (ax a i) ry (nw i) h
     TopSize a i ch -> Rectangle (ax a i) ry (nw i) (mh ch)
     Bottom -> Rectangle rx ny rw h
-    BottomH ch -> Rectangle rx ny rw (mh ch)
+    BottomH ch -> Rectangle rx (ny' ch) rw (mh ch)
     BottomW a i -> Rectangle (ax a i) ny (nw i) h
     BottomP l r -> Rectangle (rx + fi l) ny (rw - fi l - fi r) h
     BottomSize a i ch  -> Rectangle (ax a i) (ny' ch) (nw i) (mh ch)
diff --git a/xmobar.cabal b/xmobar.cabal
--- a/xmobar.cabal
+++ b/xmobar.cabal
@@ -1,6 +1,6 @@
 name:               xmobar
-version:            0.41
-homepage:           http://xmobar.org
+version:            0.42
+homepage:           https://github.com/jaor/xmobar
 synopsis:           A Minimalistic Text Based Status Bar
 description: 	    Xmobar is a minimalistic text based status bar.
                     .
@@ -110,31 +110,38 @@
                    Xmobar.Config.Types,
                    Xmobar.Config.Parse,
                    Xmobar.Run.Types,
+                   Xmobar.Run.Timer,
                    Xmobar.Run.Template,
                    Xmobar.Run.Exec,
-                   Xmobar.Run.Command,
                    Xmobar.Run.Runnable
-                   Xmobar.App.EventLoop,
+                   Xmobar.Run.Actions,
+                   Xmobar.Run.Parsers,
+                   Xmobar.Run.Loop,
                    Xmobar.App.Config,
                    Xmobar.App.Main,
                    Xmobar.App.Opts,
                    Xmobar.App.Compile,
-                   Xmobar.App.Timer,
                    Xmobar.System.Utils,
                    Xmobar.System.StatFS,
                    Xmobar.System.Environment,
                    Xmobar.System.Localize,
                    Xmobar.System.Signal,
                    Xmobar.System.Kbd,
-                   Xmobar.X11.Actions,
+                   Xmobar.Text.Ansi,
+                   Xmobar.Text.Loop,
+                   Xmobar.Text.Pango,
+                   Xmobar.Text.Swaybar,
+                   Xmobar.Text.SwaybarClicks,
+                   Xmobar.Text.Output,
                    Xmobar.X11.Events,
-                   Xmobar.X11.Parsers,
+                   Xmobar.X11.Loop,
                    Xmobar.X11.Types,
                    Xmobar.X11.Text,
                    Xmobar.X11.Bitmap,
                    Xmobar.X11.ColorCache,
                    Xmobar.X11.Window,
                    Xmobar.X11.Draw,
+                   Xmobar.Plugins.Command,
                    Xmobar.Plugins.BufferedPipeReader,
                    Xmobar.Plugins.CommandReader,
                    Xmobar.Plugins.Date,
@@ -179,24 +186,25 @@
     ghc-options: -funbox-strict-fields -Wall -fno-warn-unused-do-bind
 
     build-depends:
-      base >= 4.11.0 && < 4.16,
-      containers,
-      regex-compat,
-      process,
-      old-locale,
-      bytestring >= 0.10.8.2,
-      directory,
-      unix,
-      time,
-      filepath,
-      transformers,
-      X11 >= 1.6.1,
-      mtl >= 2.1 && < 2.3,
-      parsec == 3.1.*,
-      parsec-numbers >= 0.1.0,
-      stm >= 2.3 && < 2.6,
-      extensible-exceptions == 0.1.*,
-      async
+                  X11 >= 1.6.1,
+                  aeson >= 1.4.7.1,
+                  async,
+                  base >= 4.11.0 && < 4.16,
+                  bytestring >= 0.10.8.2,
+                  containers,
+                  directory,
+                  extensible-exceptions == 0.1.*,
+                  filepath,
+                  mtl >= 2.1 && < 2.3,
+                  old-locale,
+                  parsec == 3.1.*,
+                  parsec-numbers >= 0.1.0,
+                  process,
+                  regex-compat,
+                  stm >= 2.3 && < 2.6,
+                  time,
+                  transformers,
+                  unix
 
     if impl(ghc < 8.0.2)
        -- Disable building with GHC before 8.0.2.
@@ -381,7 +389,7 @@
                  Xmobar.Plugins.Monitors.CpuSpec
                  Xmobar.Plugins.Monitors.Common.Run
                  Xmobar.Run.Exec
-                 Xmobar.App.Timer
+                 Xmobar.Run.Timer
                  Xmobar.System.Signal
 
   if flag(with_alsa) || flag(all_extensions)
