diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,13 @@
-## Version 0.39 (unreleased)
+## Version 0.40 (November, 2021)
+
+_New features_
+
+  - New plugin: `QueueReader` (Guy Gastineau).
+  - Greatly improved FreeBSD support: Mem, Network and Swap monitors
+    fixes, and CI build for FreeBSD (Michal Zielonka).
+  - New template markup: `<hspace>`(tulthix)
+
+## Version 0.39 (August, 2021)
 
 _New features_
 
diff --git a/doc/plugins.org b/doc/plugins.org
--- a/doc/plugins.org
+++ b/doc/plugins.org
@@ -1,35 +1,37 @@
 * 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.
+  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.
+  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.
+  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.
+  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
+  #+begin_src haskell
+    Run Battery
+      [ "--template", "<acstatus>"
+      , "--Low"     , "15"
+      -- battery specific options start here.
+      , "--"
+      , "--off"     , "<left> (<timeleft>)"
+      ]
+      100
+  #+end_src
 
 ** Icon Patterns
 
@@ -1144,7 +1146,7 @@
  - 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.
+   extracted. Here is [[https://weather.rap.ucar.edu/surface/stations.txt][a sample list of station IDs]].
 
 *** =WeatherX StationID SkyConditions Args RefreshRate=
 
@@ -1260,8 +1262,8 @@
 
 * Interfacing with Window Managers
 
-Listed below are ways to interface xmobar with your window manager of
-choice.
+  Listed below are ways to interface xmobar with your window manager
+  of choice.
 
 ** Property-based Logging
 *** =XMonadLog=
@@ -1436,71 +1438,116 @@
      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:
+  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=
+  =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.
+  - 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.:
+  E.g.:
 
-#+begin_src haskell
-  Run Com "uname" ["-s","-r"] "" 0
-#+end_src
+  #+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
+  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
+  #+begin_src haskell
+    Run Com "date" ["+\"%a %b %_d %H:%M\""] "mydate" 600
+  #+end_src
 
-can be used in the output template as =%mydate%=.
+  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:
+  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=
+    =ComX ProgramName Args ExitMessage Alias RefreshRate=
 
-Works like =Com=, but displaying =ExitMessage= (a string) if the
-execution fails. For instance:
+  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
+  #+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.
+  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.
+  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=
+  - 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:
+  An example using the =dbus-send= command line utility:
 
-#+begin_src shell
+  #+begin_src shell
   dbus-send \
       --session \
       --dest=org.Xmobar.Control \
@@ -1509,82 +1556,82 @@
       '/org/Xmobar/Control' \
       org.Xmobar.Control.SendSignal \
       "string:Toggle 0"
-#+end_src
+  #+end_src
 
-It is also possible to send multiple signals at once:
+  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
+  #+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.
+  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.
+   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
+   #+begin_src haskell
+     ((0, xK_Alt_L), pure ())
+   #+end_src
 
-Also, install =avoidStruts= layout modifier from
-=XMonad.Hooks.ManageDocks=
+   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).
+   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
+   #+begin_src haskell
+     import qualified XMonad.Util.ExtensibleState as XS
 
-  data DockToggleTime = DTT { lastTime :: Time } deriving (Eq, Show, Typeable)
+     data DockToggleTime = DTT { lastTime :: Time } deriving (Eq, Show, Typeable)
 
-  instance ExtensionClass DockToggleTime where
-      initialValue = DTT 0
+     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 ()
+     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
-                      }
+         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)
+     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
+     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
diff --git a/doc/quick-start.org b/doc/quick-start.org
--- a/doc/quick-start.org
+++ b/doc/quick-start.org
@@ -215,6 +215,10 @@
 - =<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.
+
 - =<icon=/path/to/icon.xbm/>= will insert the given bitmap. XPM image
   format is also supported when compiled with the =with_xpm= flag.
 
diff --git a/readme.org b/readme.org
--- a/readme.org
+++ b/readme.org
@@ -129,21 +129,22 @@
 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, 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 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,
-aEdward Z. Yang, Leo Zhang, and Norbert Zeh.
+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 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
 
diff --git a/src/Xmobar.hs b/src/Xmobar.hs
--- a/src/Xmobar.hs
+++ b/src/Xmobar.hs
@@ -33,6 +33,7 @@
 #endif
               , module Xmobar.Plugins.EWMH
               , module Xmobar.Plugins.HandleReader
+              , module Xmobar.Plugins.QueueReader
               , module Xmobar.Plugins.Kbd
               , module Xmobar.Plugins.Locks
 #ifdef INOTIFY
@@ -60,6 +61,7 @@
 #endif
 import Xmobar.Plugins.EWMH
 import Xmobar.Plugins.HandleReader
+import Xmobar.Plugins.QueueReader
 import Xmobar.Plugins.Kbd
 import Xmobar.Plugins.Locks
 #ifdef INOTIFY
diff --git a/src/Xmobar/App/EventLoop.hs b/src/Xmobar/App/EventLoop.hs
--- a/src/Xmobar/App/EventLoop.hs
+++ b/src/Xmobar/App/EventLoop.hs
@@ -265,6 +265,7 @@
       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'))
diff --git a/src/Xmobar/Plugins/Monitors/Batt.hs b/src/Xmobar/Plugins/Monitors/Batt.hs
--- a/src/Xmobar/Plugins/Monitors/Batt.hs
+++ b/src/Xmobar/Plugins/Monitors/Batt.hs
@@ -17,45 +17,19 @@
 
 module Xmobar.Plugins.Monitors.Batt ( battConfig, runBatt, runBatt' ) where
 
-import System.Process (system)
-import Control.Monad (void, unless)
+import Xmobar.Plugins.Monitors.Batt.Common (BattOpts(..)
+                                           , Result(..)
+                                           , Status(..))
 import Xmobar.Plugins.Monitors.Common
-import Control.Exception (SomeException, handle)
-import System.FilePath ((</>))
-import System.IO (IOMode(ReadMode), hGetLine, withFile)
-import System.Posix.Files (fileExist)
-#ifdef FREEBSD
-import System.BSD.Sysctl (sysctlReadInt)
-#endif
 import System.Console.GetOpt
-import Data.List (sort, sortBy, group)
-import Data.Maybe (fromMaybe)
-import Data.Ord (comparing)
-import Text.Read (readMaybe)
 
-data BattOpts = BattOpts
-  { onString :: String
-  , offString :: String
-  , idleString :: String
-  , posColor :: Maybe String
-  , lowWColor :: Maybe String
-  , mediumWColor :: Maybe String
-  , highWColor :: Maybe String
-  , lowThreshold :: Float
-  , highThreshold :: Float
-  , onLowAction :: Maybe String
-  , actionThreshold :: Float
-  , onlineFile :: FilePath
-  , scale :: Float
-  , onIconPattern :: Maybe IconPattern
-  , offIconPattern :: Maybe IconPattern
-  , idleIconPattern :: Maybe IconPattern
-  , lowString :: String
-  , mediumString :: String
-  , highString :: String
-  , incPerc :: Bool
-  }
+#if defined(freebsd_HOST_OS)
+import qualified Xmobar.Plugins.Monitors.Batt.FreeBSD as MB
+#else
+import qualified Xmobar.Plugins.Monitors.Batt.Linux as MB
+#endif
 
+
 defaultOpts :: BattOpts
 defaultOpts = BattOpts
   { onString = "On"
@@ -108,34 +82,11 @@
   , Option "" ["highs"] (ReqArg (\x o -> o { highString = x }) "") ""
   ]
 
-data Status = Charging | Discharging | Full | Idle | Unknown deriving (Read, Eq)
--- Result perc watts time-seconds Status
-data Result = Result Float Float Float Status | NA
-
-sysDir :: FilePath
-sysDir = "/sys/class/power_supply"
-
 battConfig :: IO MConfig
 battConfig = mkMConfig
        "Batt: <watts>, <left>% / <timeleft>" -- template
        ["leftbar", "leftvbar", "left", "acstatus", "timeleft", "watts", "leftipat"] -- replacements
 
-data Files = Files
-  { fFull :: String
-  , fNow :: String
-  , fVoltage :: String
-  , fCurrent :: String
-  , fStatus :: String
-  , isCurrent :: Bool
-  } | NoFiles deriving Eq
-
-data Battery = Battery
-  { full :: !Float
-  , now :: !Float
-  , power :: !Float
-  , status :: !String
-  }
-
 data BatteryStatus
   = BattHigh
   | BattMedium
@@ -153,130 +104,13 @@
  where
    c = 100 * min 1 charge
 
-maybeAlert :: BattOpts -> Float -> IO ()
-maybeAlert opts left =
-  case onLowAction opts of
-    Nothing -> return ()
-    Just x -> unless (isNaN left || actionThreshold opts < 100 * left)
-                $ void $ system x
-
--- | FreeBSD battery query
-#ifdef FREEBSD
-battStatusFbsd :: Int -> Status
-battStatusFbsd x
-  | x == 1 = Discharging
-  | x == 2 = Charging
-  | otherwise = Unknown
-
-readBatteriesFbsd :: BattOpts -> IO Result
-readBatteriesFbsd opts = do
-  lf <- sysctlReadInt "hw.acpi.battery.life"
-  rt <- sysctlReadInt "hw.acpi.battery.rate"
-  tm <- sysctlReadInt "hw.acpi.battery.time"
-  st <- sysctlReadInt "hw.acpi.battery.state"
-  acline <- sysctlReadInt "hw.acpi.acline"
-  let p = fromIntegral lf / 100
-      w = fromIntegral rt
-      t = fromIntegral tm * 60
-      ac = acline == 1
-      -- battery full when rate is 0 and on ac.
-      sts = if (w == 0 && ac) then Full else (battStatusFbsd $ fromIntegral st)
-  unless ac (maybeAlert opts p)
-  return (Result p w t sts)
-
-#else
--- | query linux battery
-safeFileExist :: String -> String -> IO Bool
-safeFileExist d f = handle noErrors $ fileExist (d </> f)
-  where noErrors = const (return False) :: SomeException -> IO Bool
-
-batteryFiles :: String -> IO Files
-batteryFiles bat =
-  do is_charge <- exists "charge_now"
-     is_energy <- if is_charge then return False else exists "energy_now"
-     is_power <- exists "power_now"
-     plain <- exists (if is_charge then "charge_full" else "energy_full")
-     let cf = if is_power then "power_now" else "current_now"
-         sf = if plain then "" else "_design"
-     return $ case (is_charge, is_energy) of
-       (True, _) -> files "charge" cf sf is_power
-       (_, True) -> files "energy" cf sf is_power
-       _ -> NoFiles
-  where prefix = sysDir </> bat
-        exists = safeFileExist prefix
-        files ch cf sf ip = Files { fFull = prefix </> ch ++ "_full" ++ sf
-                                  , fNow = prefix </> ch ++ "_now"
-                                  , fCurrent = prefix </> cf
-                                  , fVoltage = prefix </> "voltage_now"
-                                  , fStatus = prefix </> "status"
-                                  , isCurrent = not ip}
-
-haveAc :: FilePath -> IO Bool
-haveAc f =
-  handle onError $ withFile (sysDir </> f) ReadMode (fmap (== "1") . hGetLine)
-  where onError = const (return False) :: SomeException -> IO Bool
-
-readBattery :: Float -> Files -> IO Battery
-readBattery _ NoFiles = return $ Battery 0 0 0 "Unknown"
-readBattery sc files =
-    do a <- grab $ fFull files
-       b <- grab $ fNow files
-       d <- grab $ fCurrent files
-       s <- grabs $ fStatus files
-       let sc' = if isCurrent files then sc / 10 else sc
-           a' = max a b -- sometimes the reported max charge is lower than
-       return $ Battery (3600 * a' / sc') -- wattseconds
-                        (3600 * b / sc') -- wattseconds
-                        (abs d / sc') -- watts
-                        s -- string: Discharging/Charging/Full
-    where grab f = handle onError $ withFile f ReadMode (fmap read . hGetLine)
-          onError = const (return (-1)) :: SomeException -> IO Float
-          grabs f = handle onError' $ withFile f ReadMode hGetLine
-          onError' = const (return "Unknown") :: SomeException -> IO String
-
--- sortOn is only available starting at ghc 7.10
-sortOn :: Ord b => (a -> b) -> [a] -> [a]
-sortOn f =
-  map snd . sortBy (comparing fst) . map (\x -> let y = f x in y `seq` (y, x))
-
-mostCommonDef :: Eq a => a -> [a] -> a
-mostCommonDef x xs = head $ last $ [x] : sortOn length (group xs)
-
-readBatteriesLinux :: BattOpts -> [Files] -> IO Result
-readBatteriesLinux opts bfs =
-    do let bfs' = filter (/= NoFiles) bfs
-       bats <- mapM (readBattery (scale opts)) (take 3 bfs')
-       ac <- haveAc (onlineFile opts)
-       let sign = if ac then 1 else -1
-           ft = sum (map full bats)
-           left = if ft > 0 then sum (map now bats) / ft else 0
-           watts = sign * sum (map power bats)
-           time = if watts == 0 then 0 else max 0 (sum $ map time' bats)
-           mwatts = if watts == 0 then 1 else sign * watts
-           time' b = (if ac then full b - now b else now b) / mwatts
-           statuses :: [Status]
-           statuses = map (fromMaybe Unknown . readMaybe)
-                          (sort (map status bats))
-           acst = mostCommonDef Unknown $ filter (Unknown/=) statuses
-           racst | acst /= Unknown = acst
-                 | time == 0 = Idle
-                 | ac = Charging
-                 | otherwise = Discharging
-       unless ac (maybeAlert opts left)
-       return $ if isNaN left then NA else Result left watts time racst
-#endif
-
 runBatt :: [String] -> Monitor String
 runBatt = runBatt' ["BAT", "BAT0", "BAT1", "BAT2"]
 
 runBatt' :: [String] -> [String] -> Monitor String
 runBatt' bfs args = do
   opts <- io $ parseOptsWith options defaultOpts args
-#ifdef FREEBSD
-  c <- io $ readBatteriesFbsd opts
-#else
-  c <- io $ readBatteriesLinux opts =<< mapM batteryFiles bfs
-#endif
+  c <- io $ MB.readBatteries opts bfs
   formatResult c opts
 
 formatResult :: Result -> BattOpts -> Monitor String
diff --git a/src/Xmobar/Plugins/Monitors/Batt/Common.hs b/src/Xmobar/Plugins/Monitors/Batt/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Batt/Common.hs
@@ -0,0 +1,57 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Batt.Common
+-- Copyright   :  (c) 2010, 2011, 2012, 2013, 2015, 2016, 2018, 2019 Jose A Ortega
+--                (c) 2010 Andrea Rossato, Petr Rockai
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A battery monitor for Xmobar
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Batt.Common (BattOpts(..)
+                                           , Result(..)
+                                           , Status(..)
+                                           , maybeAlert) where
+
+import System.Process (system)
+import Control.Monad (unless, void)
+import Xmobar.Plugins.Monitors.Common
+
+data Status = Charging | Discharging | Full | Idle | Unknown deriving (Read, Eq)
+-- Result perc watts time-seconds Status
+data Result = Result Float Float Float Status | NA
+
+data BattOpts = BattOpts
+  { onString :: String
+  , offString :: String
+  , idleString :: String
+  , posColor :: Maybe String
+  , lowWColor :: Maybe String
+  , mediumWColor :: Maybe String
+  , highWColor :: Maybe String
+  , lowThreshold :: Float
+  , highThreshold :: Float
+  , onLowAction :: Maybe String
+  , actionThreshold :: Float
+  , onlineFile :: FilePath
+  , scale :: Float
+  , onIconPattern :: Maybe IconPattern
+  , offIconPattern :: Maybe IconPattern
+  , idleIconPattern :: Maybe IconPattern
+  , lowString :: String
+  , mediumString :: String
+  , highString :: String
+  , incPerc :: Bool
+  }
+
+maybeAlert :: BattOpts -> Float -> IO ()
+maybeAlert opts left =
+  case onLowAction opts of
+    Nothing -> return ()
+    Just x -> unless (isNaN left || actionThreshold opts < 100 * left)
+                $ void $ system x
diff --git a/src/Xmobar/Plugins/Monitors/Batt/FreeBSD.hs b/src/Xmobar/Plugins/Monitors/Batt/FreeBSD.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Batt/FreeBSD.hs
@@ -0,0 +1,46 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Batt.FreeBSD
+-- Copyright   :  (c) 2010, 2011, 2012, 2013, 2015, 2016, 2018, 2019 Jose A Ortega
+--                (c) 2010 Andrea Rossato, Petr Rockai
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A battery monitor for Xmobar
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Batt.FreeBSD (readBatteries) where
+
+import Xmobar.Plugins.Monitors.Batt.Common (BattOpts(..)
+                                           , Result(..)
+                                           , Status(..)
+                                           , maybeAlert)
+
+import Control.Monad (unless)
+import System.BSD.Sysctl (sysctlReadInt)
+
+battStatus :: Int -> Status
+battStatus x
+  | x == 1 = Discharging
+  | x == 2 = Charging
+  | otherwise = Unknown
+
+readBatteries :: BattOpts -> [String] -> IO Result
+readBatteries opts _ = do
+  lf <- sysctlReadInt "hw.acpi.battery.life"
+  rt <- sysctlReadInt "hw.acpi.battery.rate"
+  tm <- sysctlReadInt "hw.acpi.battery.time"
+  st <- sysctlReadInt "hw.acpi.battery.state"
+  acline <- sysctlReadInt "hw.acpi.acline"
+  let p = fromIntegral lf / 100
+      w = fromIntegral rt
+      t = fromIntegral tm * 60
+      ac = acline == 1
+      -- battery full when rate is 0 and on ac.
+      sts = if w == 0 && ac then Full else battStatus $ fromIntegral st
+  unless ac (maybeAlert opts p)
+  return (Result p w t sts)
diff --git a/src/Xmobar/Plugins/Monitors/Batt/Linux.hs b/src/Xmobar/Plugins/Monitors/Batt/Linux.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Batt/Linux.hs
@@ -0,0 +1,130 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Batt.Linux
+-- Copyright   :  (c) 2010, 2011, 2012, 2013, 2015, 2016, 2018, 2019 Jose A Ortega
+--                (c) 2010 Andrea Rossato, Petr Rockai
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A battery monitor for Xmobar
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Batt.Linux (readBatteries) where
+
+import Xmobar.Plugins.Monitors.Batt.Common (BattOpts(..)
+                                           , Result(..)
+                                           , Status(..)
+                                           , maybeAlert)
+
+import Control.Monad (unless)
+import Control.Exception (SomeException, handle)
+import System.FilePath ((</>))
+import System.IO (IOMode(ReadMode), hGetLine, withFile)
+import System.Posix.Files (fileExist)
+import Data.List (sort, sortBy, group)
+import Data.Maybe (fromMaybe)
+import Data.Ord (comparing)
+import Text.Read (readMaybe)
+
+data Files = Files
+  { fFull :: String
+  , fNow :: String
+  , fVoltage :: String
+  , fCurrent :: String
+  , fStatus :: String
+  , isCurrent :: Bool
+  } | NoFiles deriving Eq
+
+data Battery = Battery
+  { full :: !Float
+  , now :: !Float
+  , power :: !Float
+  , status :: !String
+  }
+
+sysDir :: FilePath
+sysDir = "/sys/class/power_supply"
+
+safeFileExist :: String -> String -> IO Bool
+safeFileExist d f = handle noErrors $ fileExist (d </> f)
+  where noErrors = const (return False) :: SomeException -> IO Bool
+
+batteryFiles :: String -> IO Files
+batteryFiles bat =
+  do is_charge <- exists "charge_now"
+     is_energy <- if is_charge then return False else exists "energy_now"
+     is_power <- exists "power_now"
+     plain <- exists (if is_charge then "charge_full" else "energy_full")
+     let cf = if is_power then "power_now" else "current_now"
+         sf = if plain then "" else "_design"
+     return $ case (is_charge, is_energy) of
+       (True, _) -> files "charge" cf sf is_power
+       (_, True) -> files "energy" cf sf is_power
+       _ -> NoFiles
+  where prefix = sysDir </> bat
+        exists = safeFileExist prefix
+        files ch cf sf ip = Files { fFull = prefix </> ch ++ "_full" ++ sf
+                                  , fNow = prefix </> ch ++ "_now"
+                                  , fCurrent = prefix </> cf
+                                  , fVoltage = prefix </> "voltage_now"
+                                  , fStatus = prefix </> "status"
+                                  , isCurrent = not ip}
+
+haveAc :: FilePath -> IO Bool
+haveAc f =
+  handle onError $ withFile (sysDir </> f) ReadMode (fmap (== "1") . hGetLine)
+  where onError = const (return False) :: SomeException -> IO Bool
+
+readBattery :: Float -> Files -> IO Battery
+readBattery _ NoFiles = return $ Battery 0 0 0 "Unknown"
+readBattery sc files =
+    do a <- grab $ fFull files
+       b <- grab $ fNow files
+       d <- grab $ fCurrent files
+       s <- grabs $ fStatus files
+       let sc' = if isCurrent files then sc / 10 else sc
+           a' = max a b -- sometimes the reported max charge is lower than
+       return $ Battery (3600 * a' / sc') -- wattseconds
+                        (3600 * b / sc') -- wattseconds
+                        (abs d / sc') -- watts
+                        s -- string: Discharging/Charging/Full
+    where grab f = handle onError $ withFile f ReadMode (fmap read . hGetLine)
+          onError = const (return (-1)) :: SomeException -> IO Float
+          grabs f = handle onError' $ withFile f ReadMode hGetLine
+          onError' = const (return "Unknown") :: SomeException -> IO String
+
+-- sortOn is only available starting at ghc 7.10
+sortOn :: Ord b => (a -> b) -> [a] -> [a]
+sortOn f =
+  map snd . sortBy (comparing fst) . map (\x -> let y = f x in y `seq` (y, x))
+
+mostCommonDef :: Eq a => a -> [a] -> a
+mostCommonDef x xs = head $ last $ [x] : sortOn length (group xs)
+
+readBatteries :: BattOpts -> [String] -> IO Result
+readBatteries opts bfs =
+    do bfs' <- mapM batteryFiles bfs
+       let bfs'' = filter (/= NoFiles) bfs'
+       bats <- mapM (readBattery (scale opts)) (take 3 bfs'')
+       ac <- haveAc (onlineFile opts)
+       let sign = if ac then 1 else -1
+           ft = sum (map full bats)
+           left = if ft > 0 then sum (map now bats) / ft else 0
+           watts = sign * sum (map power bats)
+           time = if watts == 0 then 0 else max 0 (sum $ map time' bats)
+           mwatts = if watts == 0 then 1 else sign * watts
+           time' b = (if ac then full b - now b else now b) / mwatts
+           statuses :: [Status]
+           statuses = map (fromMaybe Unknown . readMaybe)
+                          (sort (map status bats))
+           acst = mostCommonDef Unknown $ filter (Unknown/=) statuses
+           racst | acst /= Unknown = acst
+                 | time == 0 = Idle
+                 | ac = Charging
+                 | otherwise = Discharging
+       unless ac (maybeAlert opts left)
+       return $ if isNaN left then NA else Result left watts time racst
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
@@ -20,23 +20,26 @@
   ( startCpu
   , runCpu
   , cpuConfig
-  , CpuDataRef
+  , MC.CpuDataRef
   , CpuOpts
   , CpuArguments
-  , parseCpu
+  , MC.parseCpu
   , getArguments
   ) where
 
 import Xmobar.Plugins.Monitors.Common
-import qualified Data.ByteString.Lazy.Char8 as B
-import Data.IORef (IORef, newIORef, readIORef, writeIORef)
-#ifdef FREEBSD
-import System.BSD.Sysctl (sysctlPeekArray)
-#endif
+import Data.IORef (newIORef)
 import System.Console.GetOpt
 import Xmobar.App.Timer (doEveryTenthSeconds)
 import Control.Monad (void)
+import Xmobar.Plugins.Monitors.Cpu.Common (CpuData(..))
 
+#if defined(freebsd_HOST_OS)
+import qualified Xmobar.Plugins.Monitors.Cpu.FreeBSD as MC
+#else
+import qualified Xmobar.Plugins.Monitors.Cpu.Linux as MC
+#endif
+
 newtype CpuOpts = CpuOpts
   { loadIconPattern :: Maybe IconPattern
   }
@@ -94,83 +97,6 @@
     , iowaitField
     ]
 
-data CpuData = CpuData {
-      cpuUser :: !Float,
-      cpuNice :: !Float,
-      cpuSystem :: !Float,
-      cpuIdle :: !Float,
-      cpuIowait :: !Float,
-      cpuTotal :: !Float
-    }
-
-#ifdef FREEBSD
--- kern.cp_time data from the previous iteration for computing the difference
-type CpuDataRef = IORef [Word]
-
-cpuData :: IO [Word]
-cpuData = sysctlPeekArray "kern.cp_time" :: IO [Word]
-
-parseCpu :: CpuDataRef -> IO CpuData
-parseCpu cref = do
-    prev <- readIORef cref
-    curr <- cpuData
-    writeIORef cref curr
-    let diff = map fromIntegral $ zipWith (-) curr prev
-        user = diff !! 0
-        nice = diff !! 1
-        system = diff !! 2
-        intr = diff !! 3
-        idle = diff !! 4
-        total = user + nice + system + intr + idle
-    return CpuData
-      { cpuUser = user/total
-      , cpuNice = nice/total
-      , cpuSystem = (system+intr)/total
-      , cpuIdle = idle/total
-      , cpuIowait = 0
-      , cpuTotal = user/total
-      }
-#else
-type CpuDataRef = IORef [Int]
-
--- Details about the fields here: https://www.kernel.org/doc/Documentation/filesystems/proc.txt
-cpuData :: IO [Int]
-cpuData = cpuParser <$> B.readFile "/proc/stat"
-
-readInt :: B.ByteString -> Int
-readInt bs = case B.readInt bs of
-               Nothing -> 0
-               Just (i, _) -> i
-
-cpuParser :: B.ByteString -> [Int]
-cpuParser = map readInt . tail . B.words . head . B.lines
-
-convertToCpuData :: [Float] -> CpuData
-convertToCpuData (u:n:s:ie:iw:_) =
-  CpuData
-    { cpuUser = u
-    , cpuNice = n
-    , cpuSystem = s
-    , cpuIdle = ie
-    , cpuIowait = iw
-    , cpuTotal = sum [u, n, s]
-    }
-convertToCpuData args = error $ "convertToCpuData: Unexpected list" <> show args
-
-parseCpu :: CpuDataRef -> IO CpuData
-parseCpu cref =
-    do a <- readIORef cref
-       b <- cpuData
-       writeIORef cref b
-       let dif = zipWith (-) b a
-           tot = fromIntegral $ sum dif
-           safeDiv n = case tot of
-                         0 -> 0
-                         v -> fromIntegral n / v
-           percent = map safeDiv dif
-       return $ convertToCpuData percent
-#endif
-
 data Field = Field {
       fieldName :: !String,
       fieldCompute :: !ShouldCompute
@@ -235,7 +161,7 @@
 
 data CpuArguments =
   CpuArguments
-    { cpuDataRef :: !CpuDataRef
+    { cpuDataRef :: !MC.CpuDataRef
     , cpuParams :: !MonitorConfig
     , cpuArgs :: ![String]
     , cpuOpts :: !CpuOpts
@@ -247,9 +173,9 @@
 
 getArguments :: [String] -> IO CpuArguments
 getArguments cpuArgs = do
-  initCpuData <- cpuData
+  initCpuData <- MC.cpuData
   cpuDataRef <- newIORef initCpuData
-  void $ parseCpu cpuDataRef
+  void $ MC.parseCpu cpuDataRef
   cpuParams <- computeMonitorConfig cpuArgs cpuConfig
   cpuInputTemplate <- runTemplateParser cpuParams
   cpuAllTemplate <- runExportParser (pExport cpuParams)
@@ -270,7 +196,7 @@
 
 runCpu :: CpuArguments -> IO String
 runCpu args@CpuArguments {..} = do
-  cpuValue <- parseCpu cpuDataRef
+  cpuValue <- MC.parseCpu cpuDataRef
   temMonitorValues <- formatCpu args cpuValue
   let templateInput =
         TemplateInput
diff --git a/src/Xmobar/Plugins/Monitors/Cpu/Common.hs b/src/Xmobar/Plugins/Monitors/Cpu/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Cpu/Common.hs
@@ -0,0 +1,25 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Cpu.Common
+-- Copyright   :  (c) 2011, 2017 Jose Antonio Ortega Ruiz
+--                (c) 2007-2010 Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A cpu monitor for Xmobar
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Cpu.Common (CpuData(..)) where
+
+data CpuData = CpuData {
+      cpuUser :: !Float,
+      cpuNice :: !Float,
+      cpuSystem :: !Float,
+      cpuIdle :: !Float,
+      cpuIowait :: !Float,
+      cpuTotal :: !Float
+    }
diff --git a/src/Xmobar/Plugins/Monitors/Cpu/FreeBSD.hs b/src/Xmobar/Plugins/Monitors/Cpu/FreeBSD.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Cpu/FreeBSD.hs
@@ -0,0 +1,54 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Cpu.FreeBSD
+-- Copyright   :  (c) 2011, 2017 Jose Antonio Ortega Ruiz
+--                (c) 2007-2010 Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A cpu monitor for Xmobar
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Cpu.FreeBSD (parseCpu
+                                           , CpuDataRef
+                                           , cpuData) where
+
+import Xmobar.Plugins.Monitors.Cpu.Common (CpuData(..))
+import Data.IORef (IORef, readIORef, writeIORef)
+import System.BSD.Sysctl (sysctlPeekArray)
+
+-- kern.cp_time data from the previous iteration for computing the difference
+type CpuDataRef = IORef [Word]
+
+cpuData :: IO [Word]
+cpuData = sysctlPeekArray "kern.cp_time" :: IO [Word]
+
+parseCpu :: CpuDataRef -> IO CpuData
+parseCpu cref = do
+    prev <- readIORef cref
+    curr <- cpuData
+    writeIORef cref curr
+    let diff = map fromIntegral $ zipWith (-) curr prev
+        user = head diff
+        nice = diff !! 1
+        system = diff !! 2
+        intr = diff !! 3
+        idle = diff !! 4
+        total = user + nice + system + intr + idle
+        cpuUserPerc = if total > 0 then user/total else 0
+        cpuNicePerc = if total > 0 then nice/total else 0
+        cpuSystemPerc = if total > 0 then (system+intr)/total else 0
+        cpuIdlePerc = if total > 0 then idle/total else 0
+
+    return CpuData
+      { cpuUser = cpuUserPerc
+      , cpuNice = cpuNicePerc
+      , cpuSystem = cpuSystemPerc
+      , cpuIdle = cpuIdlePerc
+      , cpuIowait = 0
+      , cpuTotal = cpuUserPerc+cpuSystemPerc
+      }
diff --git a/src/Xmobar/Plugins/Monitors/Cpu/Linux.hs b/src/Xmobar/Plugins/Monitors/Cpu/Linux.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Cpu/Linux.hs
@@ -0,0 +1,61 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Cpu.Linux
+-- Copyright   :  (c) 2011, 2017 Jose Antonio Ortega Ruiz
+--                (c) 2007-2010 Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A cpu monitor for Xmobar
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Cpu.Linux (parseCpu
+                                         , CpuDataRef
+                                         , cpuData) where
+
+import Xmobar.Plugins.Monitors.Cpu.Common (CpuData(..))
+import qualified Data.ByteString.Lazy.Char8 as B
+import Data.IORef (IORef, readIORef, writeIORef)
+
+type CpuDataRef = IORef [Int]
+
+-- Details about the fields here: https://www.kernel.org/doc/Documentation/filesystems/proc.txt
+cpuData :: IO [Int]
+cpuData = cpuParser <$> B.readFile "/proc/stat"
+
+readInt :: B.ByteString -> Int
+readInt bs = case B.readInt bs of
+               Nothing -> 0
+               Just (i, _) -> i
+
+cpuParser :: B.ByteString -> [Int]
+cpuParser = map readInt . tail . B.words . head . B.lines
+
+convertToCpuData :: [Float] -> CpuData
+convertToCpuData (u:n:s:ie:iw:_) =
+  CpuData
+    { cpuUser = u
+    , cpuNice = n
+    , cpuSystem = s
+    , cpuIdle = ie
+    , cpuIowait = iw
+    , cpuTotal = sum [u, n, s]
+    }
+convertToCpuData args = error $ "convertToCpuData: Unexpected list" <> show args
+
+parseCpu :: CpuDataRef -> IO CpuData
+parseCpu cref =
+    do a <- readIORef cref
+       b <- cpuData
+       writeIORef cref b
+       let dif = zipWith (-) b a
+           tot = fromIntegral $ sum dif
+           safeDiv n = case tot of
+                         0 -> 0
+                         v -> fromIntegral n / v
+           percent = map safeDiv dif
+       return $ convertToCpuData percent
diff --git a/src/Xmobar/Plugins/Monitors/Mem.hs b/src/Xmobar/Plugins/Monitors/Mem.hs
--- a/src/Xmobar/Plugins/Monitors/Mem.hs
+++ b/src/Xmobar/Plugins/Monitors/Mem.hs
@@ -1,3 +1,4 @@
+{-#LANGUAGE CPP #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Plugins.Monitors.Mem
@@ -15,9 +16,15 @@
 module Xmobar.Plugins.Monitors.Mem (memConfig, runMem, totalMem, usedMem) where
 
 import Xmobar.Plugins.Monitors.Common
-import qualified Data.Map as M
 import System.Console.GetOpt
 
+#if defined(freebsd_HOST_OS)
+import qualified Xmobar.Plugins.Monitors.Mem.FreeBSD as MM
+#else
+import qualified Xmobar.Plugins.Monitors.Mem.Linux as MM
+#endif
+
+
 data MemOpts = MemOpts
   { usedIconPattern :: Maybe IconPattern
   , freeIconPattern :: Maybe IconPattern
@@ -49,27 +56,11 @@
         "usedratio", "freeratio", "availableratio",
         "total", "free", "buffer", "cache", "available", "used"] -- available replacements
 
-fileMEM :: IO String
-fileMEM = readFile "/proc/meminfo"
-
-parseMEM :: IO [Float]
-parseMEM =
-    do file <- fileMEM
-       let content = map words $ take 8 $ lines file
-           info = M.fromList $ map (\line -> (head line, (read $ line !! 1 :: Float) / 1024)) content
-           [total, free, buffer, cache] = map (info M.!) ["MemTotal:", "MemFree:", "Buffers:", "Cached:"]
-           available = M.findWithDefault (free + buffer + cache) "MemAvailable:" info
-           used = total - available
-           usedratio = used / total
-           freeratio = free / total
-           availableratio = available / total
-       return [usedratio, freeratio, availableratio, total, free, buffer, cache, available, used]
-
 totalMem :: IO Float
-totalMem = fmap ((*1024) . (!!1)) parseMEM
+totalMem = fmap ((*1024) . (!!1)) MM.parseMEM
 
 usedMem :: IO Float
-usedMem = fmap ((*1024) . (!!6)) parseMEM
+usedMem = fmap ((*1024) . (!!6)) MM.parseMEM
 
 formatMem :: MemOpts -> [Float] -> Monitor [String]
 formatMem opts (r:fr:ar:xs) =
@@ -84,7 +75,7 @@
 
 runMem :: [String] -> Monitor String
 runMem argv =
-    do m <- io parseMEM
+    do m <- io MM.parseMEM
        opts <- io $ parseOptsWith options defaultOpts argv
        l <- formatMem opts m
        parseTemplate l
diff --git a/src/Xmobar/Plugins/Monitors/Mem/FreeBSD.hs b/src/Xmobar/Plugins/Monitors/Mem/FreeBSD.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Mem/FreeBSD.hs
@@ -0,0 +1,45 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Mem.FreeBSD
+-- Copyright   :  (c) Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A memory monitor for Xmobar
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Mem.FreeBSD (parseMEM) where
+
+import System.BSD.Sysctl (sysctlReadUInt)
+
+parseMEM :: IO [Float]
+parseMEM = do stats <- mapM sysctlReadUInt [
+                "vm.stats.vm.v_page_size"
+                , "vm.stats.vm.v_page_count"
+                , "vm.stats.vm.v_free_count"
+                , "vm.stats.vm.v_active_count"
+                , "vm.stats.vm.v_inactive_count"
+                , "vm.stats.vm.v_wire_count"
+                , "vm.stats.vm.v_cache_count"]
+
+              let [ pagesize, totalpages, freepages, activepages, inactivepages, wiredpages, cachedpages ] = fmap fromIntegral stats
+                  usedpages = activepages + wiredpages + cachedpages
+                  availablepages = inactivepages + cachedpages + freepages
+                  bufferedpages = activepages + inactivepages + wiredpages
+
+                  available = availablepages * pagesize
+                  used = usedpages * pagesize
+                  free = freepages * pagesize
+                  cache = cachedpages * pagesize
+                  buffer = bufferedpages * pagesize
+                  total = totalpages * pagesize
+
+                  usedratio = usedpages / totalpages
+                  freeratio = freepages / totalpages
+                  availableratio = availablepages / totalpages
+
+              return [usedratio, freeratio, availableratio, total, free, buffer, cache, available, used]
diff --git a/src/Xmobar/Plugins/Monitors/Mem/Linux.hs b/src/Xmobar/Plugins/Monitors/Mem/Linux.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Mem/Linux.hs
@@ -0,0 +1,33 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Mem.Linux
+-- Copyright   :  (c) Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A memory monitor for Xmobar
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Mem.Linux (parseMEM) where
+
+import qualified Data.Map as M
+
+fileMEM :: IO String
+fileMEM = readFile "/proc/meminfo"
+
+parseMEM :: IO [Float]
+parseMEM =
+    do file <- fileMEM
+       let content = map words $ take 8 $ lines file
+           info = M.fromList $ map (\line -> (head line, (read $ line !! 1 :: Float) / 1024)) content
+           [total, free, buffer, cache] = map (info M.!) ["MemTotal:", "MemFree:", "Buffers:", "Cached:"]
+           available = M.findWithDefault (free + buffer + cache) "MemAvailable:" info
+           used = total - available
+           usedratio = used / total
+           freeratio = free / total
+           availableratio = available / total
+       return [usedratio, freeratio, availableratio, total, free, buffer, cache, available, used]
diff --git a/src/Xmobar/Plugins/Monitors/Net.hs b/src/Xmobar/Plugins/Monitors/Net.hs
--- a/src/Xmobar/Plugins/Monitors/Net.hs
+++ b/src/Xmobar/Plugins/Monitors/Net.hs
@@ -11,9 +11,10 @@
 --
 -- A net device monitor for Xmobar
 --
+
 -----------------------------------------------------------------------------
 
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE CPP #-}
 
 module Xmobar.Plugins.Monitors.Net (
                         startNet
@@ -21,19 +22,19 @@
                       ) where
 
 import Xmobar.Plugins.Monitors.Common
-
-import Data.IORef (IORef, newIORef, readIORef, writeIORef)
-import Data.Time.Clock (UTCTime, getCurrentTime, diffUTCTime)
-import Data.Word (Word64)
-import Control.Monad (forM, filterM)
-import System.Directory (getDirectoryContents, doesFileExist)
-import System.FilePath ((</>))
+import Xmobar.Plugins.Monitors.Net.Common (NetDev(..), NetDevInfo(..), NetDevRate, NetDevRef)
+import Data.IORef (newIORef, readIORef, writeIORef)
+import Data.Time.Clock (getCurrentTime, diffUTCTime)
 import System.Console.GetOpt
-import System.IO.Error (catchIOError)
-import System.IO.Unsafe (unsafeInterleaveIO)
 
-import qualified Data.ByteString.Char8 as B
+#if defined(freebsd_HOST_OS)
+import qualified Xmobar.Plugins.Monitors.Net.FreeBSD as MN
+#else
+import qualified Xmobar.Plugins.Monitors.Net.Linux as MN
+#endif
 
+import Control.Monad (forM)
+
 type DevList = [String]
 
 parseDevList :: String -> DevList
@@ -79,70 +80,11 @@
     show MBs = "MB/s"
     show GBs = "GB/s"
 
-data NetDev num = N String (NetDevInfo num) | NA deriving (Eq,Show,Read)
-data NetDevInfo num = NI | ND num num deriving (Eq,Show,Read)
-
-type NetDevRawTotal = NetDev Word64
-type NetDevRate = NetDev Float
-
-type NetDevRef = IORef (NetDevRawTotal, UTCTime)
-
--- The more information available, the better.
--- Note that names don't matter. Therefore, if only the names differ,
--- a compare evaluates to EQ while (==) evaluates to False.
-instance Ord num => Ord (NetDev num) where
-    compare NA NA             = EQ
-    compare NA _              = LT
-    compare _  NA             = GT
-    compare (N _ i1) (N _ i2) = i1 `compare` i2
-
-instance Ord num => Ord (NetDevInfo num) where
-    compare NI NI                 = EQ
-    compare NI ND {}              = LT
-    compare ND {} NI              = GT
-    compare (ND x1 y1) (ND x2 y2) = x1 `compare` x2 <> y1 `compare` y2
-
 netConfig :: IO MConfig
 netConfig = mkMConfig
     "<dev>: <rx>KB|<tx>KB"      -- template
     ["dev", "rx", "tx", "rxbar", "rxvbar", "rxipat", "txbar", "txvbar", "txipat", "up"]     -- available replacements
 
-operstateDir :: String -> FilePath
-operstateDir d = "/sys/class/net" </> d </> "operstate"
-
-existingDevs :: IO [String]
-existingDevs = getDirectoryContents "/sys/class/net" >>= filterM isDev
-  where isDev d | d `elem` excludes = return False
-                | otherwise = doesFileExist (operstateDir d)
-        excludes = [".", "..", "lo"]
-
-isUp :: String -> IO Bool
-isUp d = flip catchIOError (const $ return False) $ do
-  operstate <- B.readFile (operstateDir d)
-  return $! (head . B.lines) operstate `elem` ["up", "unknown"]
-
-readNetDev :: [String] -> IO NetDevRawTotal
-readNetDev ~[d, x, y] = do
-  up <- unsafeInterleaveIO $ isUp d
-  return $ N d (if up then ND (r x) (r y) else NI)
-    where r s | s == "" = 0
-              | otherwise = read s
-
-netParser :: B.ByteString -> IO [NetDevRawTotal]
-netParser = mapM (readNetDev . splitDevLine) . readDevLines
-  where readDevLines = drop 2 . B.lines
-        splitDevLine = map B.unpack . selectCols . filter (not . B.null) . B.splitWith (`elem` [' ',':'])
-        selectCols cols = map (cols!!) [0,1,9]
-
-findNetDev :: String -> IO NetDevRawTotal
-findNetDev dev = do
-  nds <- B.readFile "/proc/net/dev" >>= netParser
-  case filter isDev nds of
-    x:_ -> return x
-    _ -> return NA
-  where isDev (N d _) = d == dev
-        isDev NA = False
-
 formatNet :: Maybe IconPattern -> Float -> Monitor (String, String, String, String)
 formatNet mipat d = do
     s <- getConfigValue useSuffix
@@ -169,7 +111,7 @@
 parseNet :: NetDevRef -> String -> IO NetDevRate
 parseNet nref nd = do
   (n0, t0) <- readIORef nref
-  n1 <- findNetDev nd
+  n1 <- MN.findNetDev nd
   t1 <- getCurrentTime
   writeIORef nref (n1, t1)
   let scx = realToFrac (diffUTCTime t1 t0)
@@ -213,7 +155,7 @@
 
 startDynNet :: [String] -> Int -> (String -> IO ()) -> IO ()
 startDynNet a r cb = do
-  devs <- existingDevs
+  devs <- MN.existingDevs
   refs <- forM devs $ \d -> do
             t <- getCurrentTime
             nref <- newIORef (NA, t)
diff --git a/src/Xmobar/Plugins/Monitors/Net/Common.hs b/src/Xmobar/Plugins/Monitors/Net/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Net/Common.hs
@@ -0,0 +1,50 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Net.Common
+-- Copyright   :  (c) 2011, 2012, 2013, 2014, 2017, 2020 Jose Antonio Ortega Ruiz
+--                (c) 2007-2010 Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A net device monitor for Xmobar
+--
+
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Net.Common (
+                        NetDev(..)
+                      , NetDevInfo(..)
+                      , NetDevRawTotal
+                      , NetDevRate
+                      , NetDevRef
+                      ) where
+
+import Data.IORef (IORef)
+import Data.Time.Clock (UTCTime)
+import Data.Word (Word64)
+
+data NetDev num = N String (NetDevInfo num) | NA deriving (Eq,Show,Read)
+data NetDevInfo num = NI | ND num num deriving (Eq,Show,Read)
+
+type NetDevRawTotal = NetDev Word64
+type NetDevRate = NetDev Float
+
+type NetDevRef = IORef (NetDevRawTotal, UTCTime)
+
+-- The more information available, the better.
+-- Note that names don't matter. Therefore, if only the names differ,
+-- a compare evaluates to EQ while (==) evaluates to False.
+instance Ord num => Ord (NetDev num) where
+    compare NA NA             = EQ
+    compare NA _              = LT
+    compare _  NA             = GT
+    compare (N _ i1) (N _ i2) = i1 `compare` i2
+
+instance Ord num => Ord (NetDevInfo num) where
+    compare NI NI                 = EQ
+    compare NI ND {}              = LT
+    compare ND {} NI              = GT
+    compare (ND x1 y1) (ND x2 y2) = x1 `compare` x2 <> y1 `compare` y2
diff --git a/src/Xmobar/Plugins/Monitors/Net/FreeBSD.hsc b/src/Xmobar/Plugins/Monitors/Net/FreeBSD.hsc
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Net/FreeBSD.hsc
@@ -0,0 +1,118 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE CApiFFI #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Net.FreeBSD
+-- Copyright   :  (c) 2011, 2012, 2013, 2014, 2017, 2020 Jose Antonio Ortega Ruiz
+--                (c) 2007-2010 Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A net device monitor for Xmobar
+--
+
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Net.FreeBSD (
+  existingDevs
+  , findNetDev
+  ) where
+
+import Xmobar.Plugins.Monitors.Net.Common (NetDevRawTotal, NetDev(..), NetDevInfo(..))
+import Control.Exception (catch, SomeException(..))
+import Foreign (Int32, plusPtr)
+import Foreign.C.Types (CUIntMax, CUChar)
+import Foreign.C.String (peekCString)
+import Foreign.ForeignPtr ()
+import Foreign.Storable (Storable, alignment, sizeOf, peek, poke)
+import System.BSD.Sysctl (OID, sysctlPrepareOid, sysctlReadInt, sysctlPeek)
+
+#include <sys/sysctl.h>
+#include <net/if.h>
+#include <net/if_mib.h>
+
+data IfData = AvailableIfData {
+  name :: String
+  , txBytes :: CUIntMax
+  , rxBytes :: CUIntMax
+  , isUp :: Bool
+  } | NotAvailableIfData
+  deriving (Show, Read, Eq)
+
+instance Storable IfData where
+  alignment _ = #{alignment struct ifmibdata}
+  sizeOf _    = #{size struct ifmibdata}
+  peek ptr    = do
+    cname <- peekCString (ptr `plusPtr` (#offset struct ifmibdata, ifmd_name))
+    tx <- peek ((ifmd_data_ptr ptr) `plusPtr` (#offset struct if_data, ifi_obytes)) :: IO CUIntMax
+    rx <- peek ((ifmd_data_ptr ptr) `plusPtr` (#offset struct if_data, ifi_ibytes)) :: IO CUIntMax
+    state <- peek ((ifmd_data_ptr ptr) `plusPtr` (#offset struct if_data, ifi_link_state)) :: IO CUChar
+    return $ AvailableIfData {name = cname, txBytes = tx, rxBytes = rx, isUp = up state}
+      where
+        up state = state == (#const LINK_STATE_UP)
+        ifmd_data_ptr p = p `plusPtr` (#offset struct ifmibdata, ifmd_data)
+
+  poke _ _    = pure ()
+
+getNetIfCountOID :: IO OID
+getNetIfCountOID = sysctlPrepareOid [
+  #const CTL_NET
+  , #const PF_LINK
+  , #const NETLINK_GENERIC
+  , #const IFMIB_SYSTEM
+  , #const IFMIB_IFCOUNT]
+
+getNetIfDataOID :: Int32 -> IO OID
+getNetIfDataOID i = sysctlPrepareOid [
+  #const CTL_NET
+  , #const PF_LINK
+  , #const NETLINK_GENERIC
+  , #const IFMIB_IFDATA
+  , i
+  , #const IFDATA_GENERAL]
+
+getNetIfCount :: IO Int32
+getNetIfCount = do
+  oid <- getNetIfCountOID
+  sysctlReadInt oid
+
+getNetIfData :: Int32 -> IO IfData
+getNetIfData i = do
+  oid <- getNetIfDataOID i
+  res <- catch (sysctlPeek oid) (\(SomeException _) -> return NotAvailableIfData)
+  return res
+
+getAllNetworkData :: IO [IfData]
+getAllNetworkData = do
+  count <- getNetIfCount
+  result <- mapM getNetIfData [1..count]
+  return result
+
+existingDevs :: IO [String]
+existingDevs = getAllNetworkData >>= (\xs -> return $ filter (/= "lo0") $ fmap name xs)
+
+convertIfDataToNetDev :: IfData -> IO NetDevRawTotal
+convertIfDataToNetDev ifData = do
+  let up = isUp ifData
+      rx = fromInteger . toInteger $ rxBytes ifData
+      tx = fromInteger . toInteger $ txBytes ifData
+      d = name ifData
+  return $ N d (if up then ND rx tx else NI)
+
+netConvertIfDataToNetDev :: [IfData] -> IO [NetDevRawTotal]
+netConvertIfDataToNetDev = mapM convertIfDataToNetDev
+
+findNetDev :: String -> IO NetDevRawTotal
+findNetDev dev = do
+  nds <- getAllNetworkData >>= netConvertIfDataToNetDev
+  case filter isDev nds of
+    x:_ -> return x
+    _ -> return NA
+  where isDev (N d _) = d == dev
+        isDev NA = False
diff --git a/src/Xmobar/Plugins/Monitors/Net/Linux.hs b/src/Xmobar/Plugins/Monitors/Net/Linux.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Net/Linux.hs
@@ -0,0 +1,69 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Net.Linux
+-- Copyright   :  (c) 2011, 2012, 2013, 2014, 2017, 2020 Jose Antonio Ortega Ruiz
+--                (c) 2007-2010 Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A net device monitor for Xmobar
+--
+
+-----------------------------------------------------------------------------
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Xmobar.Plugins.Monitors.Net.Linux (
+  existingDevs
+  , findNetDev
+  ) where
+
+import Xmobar.Plugins.Monitors.Net.Common (NetDevRawTotal, NetDev(..), NetDevInfo(..))
+
+import Control.Monad (filterM)
+import System.Directory (getDirectoryContents, doesFileExist)
+import System.FilePath ((</>))
+import System.IO.Error (catchIOError)
+import System.IO.Unsafe (unsafeInterleaveIO)
+
+import qualified Data.ByteString.Char8 as B
+
+
+operstateDir :: String -> FilePath
+operstateDir d = "/sys/class/net" </> d </> "operstate"
+
+existingDevs :: IO [String]
+existingDevs = getDirectoryContents "/sys/class/net" >>= filterM isDev
+  where isDev d | d `elem` excludes = return False
+                | otherwise = doesFileExist (operstateDir d)
+        excludes = [".", "..", "lo"]
+
+isUp :: String -> IO Bool
+isUp d = flip catchIOError (const $ return False) $ do
+  operstate <- B.readFile (operstateDir d)
+  return $! (head . B.lines) operstate `elem` ["up", "unknown"]
+
+readNetDev :: [String] -> IO NetDevRawTotal
+readNetDev ~[d, x, y] = do
+  up <- unsafeInterleaveIO $ isUp d
+  return $ N d (if up then ND (r x) (r y) else NI)
+    where r s | s == "" = 0
+              | otherwise = read s
+
+netParser :: B.ByteString -> IO [NetDevRawTotal]
+netParser = mapM (readNetDev . splitDevLine) . readDevLines
+  where readDevLines = drop 2 . B.lines
+        splitDevLine = map B.unpack . selectCols . filter (not . B.null) . B.splitWith (`elem` [' ',':'])
+        selectCols cols = map (cols!!) [0,1,9]
+
+findNetDev :: String -> IO NetDevRawTotal
+findNetDev dev = do
+  nds <- B.readFile "/proc/net/dev" >>= netParser
+  case filter isDev nds of
+    x:_ -> return x
+    _ -> return NA
+  where isDev (N d _) = d == dev
+        isDev NA = False
diff --git a/src/Xmobar/Plugins/Monitors/Swap.hs b/src/Xmobar/Plugins/Monitors/Swap.hs
--- a/src/Xmobar/Plugins/Monitors/Swap.hs
+++ b/src/Xmobar/Plugins/Monitors/Swap.hs
@@ -1,3 +1,4 @@
+{-#LANGUAGE CPP #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Plugins.Monitors.Swap
@@ -16,31 +17,17 @@
 
 import Xmobar.Plugins.Monitors.Common
 
-import qualified Data.ByteString.Lazy.Char8 as B
+#if defined(freebsd_HOST_OS)
+import qualified Xmobar.Plugins.Monitors.Swap.FreeBSD as MS
+#else
+import qualified Xmobar.Plugins.Monitors.Swap.Linux as MS
+#endif
 
 swapConfig :: IO MConfig
 swapConfig = mkMConfig
         "Swap: <usedratio>%"                    -- template
         ["usedratio", "total", "used", "free"] -- available replacements
 
-fileMEM :: IO B.ByteString
-fileMEM = B.readFile "/proc/meminfo"
-
-parseMEM :: IO [Float]
-parseMEM =
-    do file <- fileMEM
-       let li i l
-               | l /= [] = head l !! i
-               | otherwise = B.empty
-           fs s l
-               | null l    = False
-               | otherwise = head l == B.pack s
-           get_data s = flip (/) 1024 . read . B.unpack . li 1 . filter (fs s)
-           st   = map B.words . B.lines $ file
-           tot  = get_data "SwapTotal:" st
-           free = get_data "SwapFree:" st
-       return [(tot - free) / tot, tot, tot - free, free]
-
 formatSwap :: [Float] -> Monitor [String]
 formatSwap (r:xs) = do
   d <- getConfigValue decDigits
@@ -51,6 +38,6 @@
 
 runSwap :: [String] -> Monitor String
 runSwap _ =
-    do m <- io parseMEM
+    do m <- io MS.parseMEM
        l <- formatSwap m
        parseTemplate l
diff --git a/src/Xmobar/Plugins/Monitors/Swap/FreeBSD.hsc b/src/Xmobar/Plugins/Monitors/Swap/FreeBSD.hsc
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Swap/FreeBSD.hsc
@@ -0,0 +1,102 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Swap.FreeBSD
+-- Copyright   :  (c) Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A  swap usage monitor for Xmobar
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Swap.FreeBSD (parseMEM) where
+
+import System.BSD.Sysctl (sysctlReadUInt)
+import Foreign
+import Foreign.C.Types
+import Foreign.C.String
+
+
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdlib.h>
+
+
+foreign import ccall unsafe "kvm.h kvm_open" c_kvm_open :: CString -> CString -> CString -> CInt -> CString ->  IO (Ptr KVM_T)
+foreign import ccall "&kvm_close" c_kvm_close :: FinalizerPtr KVM_T
+foreign import ccall unsafe "kvm.h kvm_getswapinfo" c_kvm_getswapinfo :: Ptr KVM_T -> Ptr KVM_SWAP -> CInt -> CInt -> IO CInt
+
+data KVM_T
+data KvmT = KvmT !(ForeignPtr KVM_T)
+  deriving (Eq, Ord, Show)
+
+data KVM_SWAP
+data KvmSwap = KvmSwap !(ForeignPtr KVM_SWAP)
+  deriving (Eq, Ord, Show)
+
+getKvmT:: IO KvmT
+getKvmT = do
+  withCString "/dev/null" $ \dir -> do
+    kvm_t_ptr <- c_kvm_open nullPtr dir nullPtr #{const O_RDONLY} nullPtr
+    ptr <- newForeignPtr c_kvm_close kvm_t_ptr
+    return $ KvmT ptr
+
+getSwapData :: KvmT -> IO SwapData
+getSwapData (KvmT kvm_t_fp) = do
+  withForeignPtr kvm_t_fp $ \kvm_t_ptr -> do
+    allocaBytes #{size struct kvm_swap} $ \swap_ptr -> do
+      c_kvm_getswapinfo kvm_t_ptr swap_ptr 1 0
+      peek $ castPtr swap_ptr :: IO SwapData
+
+data SwapData = AvailableSwapData {
+  used :: Integer
+  , total :: Integer
+  } | NotAvailableSwapData
+  deriving (Show, Read, Eq)
+
+instance Storable SwapData where
+  alignment _ = #{alignment struct kvm_swap}
+  sizeOf _    = #{size struct kvm_swap}
+  peek ptr    = do
+    cused <- #{peek struct kvm_swap, ksw_used} ptr :: IO CUInt
+    ctotal <- #{peek struct kvm_swap, ksw_total} ptr :: IO CUInt
+    return $ AvailableSwapData {used = toInteger cused, total = toInteger ctotal}
+
+  poke _ _    = pure ()
+
+
+isEnabled :: IO Bool
+isEnabled = do
+  enabled <- sysctlReadUInt "vm.swap_enabled"
+  return $ enabled == 1
+
+parseMEM' :: Bool -> IO [Float]
+parseMEM' False = return []
+parseMEM' True = do
+  kvm_t <- getKvmT
+  swap <- getSwapData kvm_t
+  pagesize <- toInteger <$> sysctlReadUInt "vm.stats.vm.v_page_size"
+
+  let
+    swapTotal = total swap
+    swapUsed = used swap
+    tot = swapTotal * pagesize
+    fr = tot - swapUsed * pagesize
+
+  return $ res (fromInteger tot) (fromInteger fr)
+  where
+    res :: Float -> Float -> [Float]
+    res _ 0 = []
+    res t f = [(t-f)/t, t, t - f, f]
+
+parseMEM :: IO [Float]
+parseMEM = do
+  enabled <- isEnabled
+  parseMEM' enabled
diff --git a/src/Xmobar/Plugins/Monitors/Swap/Linux.hs b/src/Xmobar/Plugins/Monitors/Swap/Linux.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Swap/Linux.hs
@@ -0,0 +1,35 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Swap.Linux
+-- Copyright   :  (c) Andrea Rossato
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+-- A  swap usage monitor for Xmobar
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Swap.Linux (parseMEM) where
+
+import qualified Data.ByteString.Lazy.Char8 as B
+
+fileMEM :: IO B.ByteString
+fileMEM = B.readFile "/proc/meminfo"
+
+parseMEM :: IO [Float]
+parseMEM =
+    do file <- fileMEM
+       let li i l
+               | l /= [] = head l !! i
+               | otherwise = B.empty
+           fs s l
+               | null l    = False
+               | otherwise = head l == B.pack s
+           get_data s = flip (/) 1024 . read . B.unpack . li 1 . filter (fs s)
+           st   = map B.words . B.lines $ file
+           tot  = get_data "SwapTotal:" st
+           free = get_data "SwapFree:" st
+       return [(tot - free) / tot, tot, tot - free, free]
diff --git a/src/Xmobar/Plugins/Monitors/Top.hs b/src/Xmobar/Plugins/Monitors/Top.hs
--- a/src/Xmobar/Plugins/Monitors/Top.hs
+++ b/src/Xmobar/Plugins/Monitors/Top.hs
@@ -1,3 +1,5 @@
+{-#LANGUAGE CPP #-}
+{-# LANGUAGE BangPatterns #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Plugins.Monitors.Top
@@ -12,25 +14,28 @@
 --
 -----------------------------------------------------------------------------
 
-{-# LANGUAGE ForeignFunctionInterface #-}
-{-# LANGUAGE BangPatterns #-}
-
 module Xmobar.Plugins.Monitors.Top (startTop, topMemConfig, runTopMem) where
 
 import Xmobar.Plugins.Monitors.Common
 
-import Control.Exception (SomeException, handle)
-import Data.IORef (IORef, newIORef, readIORef, writeIORef)
-import Data.List (sortBy, foldl')
+import Data.IORef (newIORef, readIORef, writeIORef)
+import Data.List (sortBy)
 import Data.Ord (comparing)
-import Data.Time.Clock (UTCTime, getCurrentTime, diffUTCTime)
-import System.Directory (getDirectoryContents)
-import System.FilePath ((</>))
-import System.IO (IOMode(ReadMode), hGetLine, withFile)
-import System.Posix.Unistd (SysVar(ClockTick), getSysVar)
+import Data.Time.Clock (getCurrentTime, diffUTCTime)
 
-import Foreign.C.Types
+import Xmobar.Plugins.Monitors.Top.Common (
+  MemInfo
+  , TimeInfo
+  , Times
+  , TimesRef)
 
+#if defined(freebsd_HOST_OS)
+import qualified Xmobar.Plugins.Monitors.Top.FreeBSD as MT
+#else
+import qualified Xmobar.Plugins.Monitors.Top.Linux as MT
+#endif
+
+
 maxEntries :: Int
 maxEntries = 10
 
@@ -47,41 +52,6 @@
                                , k <- [ "name", "cpu", "both"
                                       , "mname", "mem", "mboth"]])
 
-foreign import ccall "unistd.h getpagesize"
-  c_getpagesize :: CInt
-
-pageSize :: Float
-pageSize = fromIntegral c_getpagesize / 1024
-
-processes :: IO [FilePath]
-processes = fmap (filter isPid) (getDirectoryContents "/proc")
-  where isPid = (`elem` ['0'..'9']) . head
-
-statWords :: [String] -> [String]
-statWords line@(x:pn:ppn:xs) =
-  if last pn == ')' then line else statWords (x:(pn ++ " " ++ ppn):xs)
-statWords _ = replicate 52 "0"
-
-getProcessData :: FilePath -> IO [String]
-getProcessData pidf =
-  handle ign $ withFile ("/proc" </> pidf </> "stat") ReadMode readWords
-  where readWords = fmap (statWords . words) . hGetLine
-        ign = const (return []) :: SomeException -> IO [String]
-
-memPages :: [String] -> String
-memPages fs = fs!!23
-
-ppid :: [String] -> String
-ppid fs = fs!!3
-
-skip :: [String] -> Bool
-skip fs = length fs < 24 || memPages fs == "0" || ppid fs == "0"
-
-handleProcesses :: ([String] -> a) -> IO [a]
-handleProcesses f =
-  fmap (foldl' (\a p -> if skip p then a else f p : a) [])
-       (processes >>= mapM getProcessData)
-
 showInfo :: String -> String -> Float -> Monitor [String]
 showInfo nm sms mms = do
   mnw <- getConfigValue maxWidth
@@ -94,20 +64,10 @@
   both <- showWithColors' (rnm ++ " " ++ sms) mms
   return [nm, mstr, both]
 
-processName :: [String] -> String
-processName = drop 1 . init . (!!1)
 
 sortTop :: [(String, Float)] -> [(String, Float)]
 sortTop =  sortBy (flip (comparing snd))
 
-type MemInfo = (String, Float)
-
-meminfo :: [String] -> MemInfo
-meminfo fs = (processName fs, pageSize * parseFloat (fs!!23))
-
-meminfos :: IO [MemInfo]
-meminfos = handleProcesses meminfo
-
 showMemInfo :: Float -> MemInfo -> Monitor [String]
 showMemInfo scale (nm, rss) =
   showInfo nm (showWithUnits 3 1 rss) (100 * rss / sc)
@@ -117,30 +77,8 @@
 showMemInfos ms = mapM (showMemInfo tm) ms
   where tm = sum (map snd ms)
 
-runTopMem :: [String] -> Monitor String
-runTopMem _ = do
-  mis <- io meminfos
-  pstr <- showMemInfos (sortTop mis)
-  parseTemplate $ concat pstr
-
-type Pid = Int
-type TimeInfo = (String, Float)
-type TimeEntry = (Pid, TimeInfo)
-type Times = [TimeEntry]
-type TimesRef = IORef (Times, UTCTime)
-
-timeMemEntry :: [String] -> (TimeEntry, MemInfo)
-timeMemEntry fs = ((p, (n, t)), (n, r))
-  where p = parseInt (head fs)
-        n = processName fs
-        t = parseFloat (fs!!13) + parseFloat (fs!!14)
-        (_, r) = meminfo fs
-
-timeMemEntries :: IO [(TimeEntry, MemInfo)]
-timeMemEntries = handleProcesses timeMemEntry
-
 timeMemInfos :: IO (Times, [MemInfo], Int)
-timeMemInfos = fmap res timeMemEntries
+timeMemInfos = fmap res MT.timeMemEntries
   where res x = (sortBy (comparing fst) $ map fst x, map snd x, length x)
 
 combine :: Times -> Times -> Times
@@ -164,7 +102,7 @@
   c1 <- getCurrentTime
   let scx = realToFrac (diffUTCTime c1 c0) * scale
       !scx' = if scx > 0 then scx else scale
-      nts = map (\(_, (nm, t)) -> (nm, min 100 (t / scx'))) (combine t0 t1)
+      nts = map (\(_, (nm, t)) -> (nm, t / scx')) (combine t0 t1)
       !t1' = take' (length t1) t1
       !nts' = take' maxEntries (sortTop nts)
       !mis' = take' maxEntries (sortTop mis)
@@ -178,6 +116,12 @@
 showTimeInfos :: [TimeInfo] -> Monitor [[String]]
 showTimeInfos = mapM showTimeInfo
 
+runTopMem :: [String] -> Monitor String
+runTopMem _ = do
+  mis <- io MT.meminfos
+  pstr <- showMemInfos (sortTop mis)
+  parseTemplate $ concat pstr
+
 runTop :: TimesRef -> Float -> [String] -> Monitor String
 runTop tref scale _ = do
   (no, ps, ms) <- io $ topProcesses tref scale
@@ -187,9 +131,8 @@
 
 startTop :: [String] -> Int -> (String -> IO ()) -> IO ()
 startTop a r cb = do
-  cr <- getSysVar ClockTick
   c <- getCurrentTime
   tref <- newIORef ([], c)
-  let scale = fromIntegral cr / 100
+  scale <- MT.scale
   _ <- topProcesses tref scale
   runM a topConfig (runTop tref scale) r cb
diff --git a/src/Xmobar/Plugins/Monitors/Top/Common.hs b/src/Xmobar/Plugins/Monitors/Top/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Top/Common.hs
@@ -0,0 +1,32 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Top.Common
+-- Copyright   :  (c) 2010, 2011, 2012, 2013, 2014, 2018 Jose A Ortega Ruiz
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+--  Process activity and memory consumption monitors
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Top.Common (
+  MemInfo
+  , Pid
+  , TimeInfo
+  , TimeEntry
+  , Times
+  , TimesRef
+  ) where
+
+import Data.IORef (IORef)
+import Data.Time.Clock (UTCTime)
+
+type MemInfo = (String, Float)
+type Pid = Int
+type TimeInfo = (String, Float)
+type TimeEntry = (Pid, TimeInfo)
+type Times = [TimeEntry]
+type TimesRef = IORef (Times, UTCTime)
diff --git a/src/Xmobar/Plugins/Monitors/Top/FreeBSD.hsc b/src/Xmobar/Plugins/Monitors/Top/FreeBSD.hsc
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Top/FreeBSD.hsc
@@ -0,0 +1,143 @@
+{-# LANGUAGE CPP                      #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE EmptyDataDecls #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CApiFFI #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Top.FreeBSD
+-- Copyright   :  (c) 2010, 2011, 2012, 2013, 2014, 2018 Jose A Ortega Ruiz
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+--  Process activity and memory consumption monitors
+--
+-----------------------------------------------------------------------------
+
+module Xmobar.Plugins.Monitors.Top.FreeBSD (
+  timeMemEntries
+  , meminfos
+  , scale) where
+
+import Foreign
+import Foreign.C.Types
+import Foreign.C.String
+
+import Xmobar.Plugins.Monitors.Top.Common (MemInfo, TimeEntry)
+
+#include <unistd.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <libprocstat.h>
+
+foreign import ccall "unistd.h getpagesize" c_getpagesize :: CInt
+foreign import ccall unsafe "libprocstat.h procstat_open_sysctl" c_procstat_open_sysctl :: IO (Ptr PROCSTAT)
+foreign import ccall "&procstat_close" c_procstat_close :: FinalizerPtr PROCSTAT
+foreign import ccall "&procstat_freeprocs" c_procstat_freeprocs :: FinalizerEnvPtr PROCSTAT KINFO_PROC
+foreign import ccall unsafe "libprocstat.h procstat_getprocs" c_procstat_getprocs :: Ptr PROCSTAT -> CInt -> CInt -> Ptr CUInt -> IO (Ptr KINFO_PROC)
+
+data PROCSTAT
+data ProcStat = ProcStat !(ForeignPtr PROCSTAT)
+  deriving (Eq, Ord, Show)
+
+data KINFO_PROC
+data KinfoProc = KinfoProc [ProcData] Int
+  deriving (Eq, Show)
+
+data ProcData = ProcData {
+  pname :: String
+  , cpu :: Float
+  , tdflags :: CULong
+  , flag :: CULong
+  , stat :: CUChar
+  , rss :: Float
+  , pid :: Int
+  , runtime :: Float
+  }
+  deriving (Show, Read, Eq)
+
+instance Storable ProcData where
+  alignment _ = #{alignment struct kinfo_proc}
+  sizeOf _    = #{size struct kinfo_proc}
+  peek ptr    = do
+       c <- #{peek struct kinfo_proc, ki_pctcpu} ptr
+       ctdflags <- #{peek struct kinfo_proc, ki_tdflags} ptr
+       cflag <- #{peek struct kinfo_proc, ki_flag} ptr
+       cstat <- #{peek struct kinfo_proc, ki_stat} ptr
+       cruntime <- #{peek struct kinfo_proc, ki_runtime} ptr :: IO CULong
+       crss <- #{peek struct kinfo_proc, ki_rssize} ptr :: IO CULong
+       cname <- peekCString (ptr `plusPtr` (#offset struct kinfo_proc, ki_comm))
+       cpid <- #{peek struct kinfo_proc, ki_pid} ptr
+       let crssf = (fromIntegral . toInteger) crss
+       let cruntimef = ((fromIntegral . toInteger) cruntime  + 500000) / 10000
+       return $ ProcData {
+         pname = cname
+         , cpu = (pctdouble c) * 100
+         , tdflags = ctdflags
+         , stat = cstat
+         , flag = cflag
+         , rss = crssf * pageSize
+         , pid = cpid
+         , runtime = cruntimef}
+
+  poke _ _    = pure ()
+
+pctdouble :: Int -> Float
+pctdouble p = (fromIntegral p) / #{const FSCALE}
+
+
+pageSize :: Float
+pageSize = fromIntegral c_getpagesize / 1024
+
+
+getProcStat:: IO ProcStat
+getProcStat = do
+    proc_ptr <- c_procstat_open_sysctl
+    ptr <- newForeignPtr c_procstat_close proc_ptr
+    return $ ProcStat ptr
+
+
+getProcessesInfo :: ProcStat -> IO [ProcData]
+getProcessesInfo (ProcStat ps_fp) = do
+  withForeignPtr ps_fp $ \ps_ptr -> do
+    alloca $ \n_ptr -> do
+      kinfo_proc_ptr <- c_procstat_getprocs ps_ptr #{const KERN_PROC_PROC} 0 n_ptr
+      newForeignPtrEnv c_procstat_freeprocs ps_ptr kinfo_proc_ptr
+      num <- peek (n_ptr :: Ptr CUInt)
+      pds <- peekArray (fromIntegral num) $ castPtr kinfo_proc_ptr :: IO [ProcData]
+
+      return $ [p | p <- pds, flag p .&. #{const P_SYSTEM} == 0]
+
+
+processes :: IO [ProcData]
+processes = do
+  proc_stat <- getProcStat
+  getProcessesInfo proc_stat
+
+handleProcesses :: (ProcData -> a) -> IO [a]
+handleProcesses f = do
+  ps <- processes
+  return $ fmap (\pd -> f pd) ps
+
+meminfo :: ProcData -> MemInfo
+meminfo pd = (pname pd, rss pd)
+
+meminfos :: IO [MemInfo]
+meminfos = handleProcesses meminfo
+
+timeMemEntry :: ProcData -> (TimeEntry, MemInfo)
+timeMemEntry pd = ((p, (n, t)), (n, r))
+  where p = pid pd
+        n = pname pd
+        t = runtime pd
+        (_, r) = meminfo pd
+
+timeMemEntries :: IO [(TimeEntry, MemInfo)]
+timeMemEntries = handleProcesses timeMemEntry
+
+scale :: IO Float
+scale = return 1
diff --git a/src/Xmobar/Plugins/Monitors/Top/Linux.hs b/src/Xmobar/Plugins/Monitors/Top/Linux.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Top/Linux.hs
@@ -0,0 +1,92 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Plugins.Monitors.Top.Linux
+-- Copyright   :  (c) 2010, 2011, 2012, 2013, 2014, 2018 Jose A Ortega Ruiz
+-- License     :  BSD-style (see LICENSE)
+--
+-- Maintainer  :  Jose A Ortega Ruiz <jao@gnu.org>
+-- Stability   :  unstable
+-- Portability :  unportable
+--
+--  Process activity and memory consumption monitors
+--
+-----------------------------------------------------------------------------
+
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Xmobar.Plugins.Monitors.Top.Linux (
+  timeMemEntries
+  , meminfos
+  , scale) where
+
+import Xmobar.Plugins.Monitors.Common (parseFloat, parseInt)
+import Xmobar.Plugins.Monitors.Top.Common (MemInfo, TimeEntry)
+
+import Control.Exception (SomeException, handle)
+import Data.List (foldl')
+import System.Directory (getDirectoryContents)
+import System.FilePath ((</>))
+import System.IO (IOMode(ReadMode), hGetLine, withFile)
+import System.Posix.Unistd (SysVar(ClockTick), getSysVar)
+
+import Foreign.C.Types
+
+foreign import ccall "unistd.h getpagesize"
+  c_getpagesize :: CInt
+
+pageSize :: Float
+pageSize = fromIntegral c_getpagesize / 1024
+
+processes :: IO [FilePath]
+processes = fmap (filter isPid) (getDirectoryContents "/proc")
+  where isPid = (`elem` ['0'..'9']) . head
+
+statWords :: [String] -> [String]
+statWords line@(x:pn:ppn:xs) =
+  if last pn == ')' then line else statWords (x:(pn ++ " " ++ ppn):xs)
+statWords _ = replicate 52 "0"
+
+getProcessData :: FilePath -> IO [String]
+getProcessData pidf =
+  handle ign $ withFile ("/proc" </> pidf </> "stat") ReadMode readWords
+  where readWords = fmap (statWords . words) . hGetLine
+        ign = const (return []) :: SomeException -> IO [String]
+
+memPages :: [String] -> String
+memPages fs = fs!!23
+
+ppid :: [String] -> String
+ppid fs = fs!!3
+
+skip :: [String] -> Bool
+skip fs = length fs < 24 || memPages fs == "0" || ppid fs == "0"
+
+handleProcesses :: ([String] -> a) -> IO [a]
+handleProcesses f =
+  fmap (foldl' (\a p -> if skip p then a else f p : a) [])
+       (processes >>= mapM getProcessData)
+
+processName :: [String] -> String
+processName = drop 1 . init . (!!1)
+
+meminfo :: [String] -> MemInfo
+meminfo fs = (processName fs, pageSize * parseFloat (fs!!23))
+
+meminfos :: IO [MemInfo]
+meminfos = handleProcesses meminfo
+
+timeMemEntry :: [String] -> (TimeEntry, MemInfo)
+timeMemEntry fs = ((p, (n, t)), (n, r))
+  where p = parseInt (head fs)
+        n = processName fs
+        t = parseFloat (fs!!13) + parseFloat (fs!!14)
+        (_, r) = meminfo fs
+
+timeMemEntries :: IO [(TimeEntry, MemInfo)]
+timeMemEntries = handleProcesses timeMemEntry
+
+
+scale :: IO Float
+scale = do
+  cr <- getSysVar ClockTick
+  return $ fromIntegral cr / 100
diff --git a/src/Xmobar/Plugins/Monitors/Uptime.hs b/src/Xmobar/Plugins/Monitors/Uptime.hs
--- a/src/Xmobar/Plugins/Monitors/Uptime.hs
+++ b/src/Xmobar/Plugins/Monitors/Uptime.hs
@@ -1,3 +1,5 @@
+{-#LANGUAGE CPP #-}
+
 ------------------------------------------------------------------------------
 -- |
 -- Module      : Plugins.Monitors.Uptime
@@ -19,22 +21,22 @@
 
 import Xmobar.Plugins.Monitors.Common
 
-import qualified Data.ByteString.Lazy.Char8 as B
+#if defined(freebsd_HOST_OS)
+import qualified Xmobar.Plugins.Monitors.Uptime.FreeBSD as MU
+#else
+import qualified Xmobar.Plugins.Monitors.Uptime.Linux as MU
+#endif
 
 uptimeConfig :: IO MConfig
 uptimeConfig = mkMConfig "Up <days>d <hours>h <minutes>m"
                          ["days", "hours", "minutes", "seconds"]
 
-readUptime :: IO Float
-readUptime =
-  fmap (read . B.unpack . head . B.words) (B.readFile "/proc/uptime")
-
 secsPerDay :: Integer
 secsPerDay = 24 * 3600
 
 uptime :: Monitor [String]
 uptime = do
-  t <- io readUptime
+  t <- io MU.readUptime
   u <- getConfigValue useSuffix
   let tsecs = floor t
       secs = tsecs `mod` secsPerDay
diff --git a/src/Xmobar/Plugins/Monitors/Uptime/FreeBSD.hsc b/src/Xmobar/Plugins/Monitors/Uptime/FreeBSD.hsc
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Uptime/FreeBSD.hsc
@@ -0,0 +1,52 @@
+------------------------------------------------------------------------------
+-- |
+-- Module      : Plugins.Monitors.Uptime.FreeBSD
+-- Copyright   : (c) 2010 Jose Antonio Ortega Ruiz
+-- License     : BSD3-style (see LICENSE)
+--
+-- Maintainer  : jao@gnu.org
+-- Stability   : unstable
+-- Portability : unportable
+-- Created: Sun Dec 12, 2010 20:26
+--
+--
+-- Uptime
+--
+------------------------------------------------------------------------------
+
+
+module Xmobar.Plugins.Monitors.Uptime.FreeBSD (readUptime) where
+
+import Data.Time.Clock.POSIX (getPOSIXTime)
+import System.BSD.Sysctl
+import Data.Int
+import Foreign.C
+import Foreign.Storable
+
+#include <sys/types.h>
+#include <sys/user.h>
+#include <sys/time.h>
+#include <sys/sysctl.h>
+
+data TimeVal = TimeVal {sec:: CTime}
+
+instance Storable TimeVal where
+  sizeOf _    = #{size struct timeval}
+  alignment _ = alignment (undefined::CTime)
+  peek ptr    = do cSec  <- #{peek struct timeval, tv_sec} ptr
+                   return (TimeVal cSec)
+  poke _ _    = pure ()
+
+now :: IO Int64
+now = do
+    posix <- getPOSIXTime
+    return $ round posix
+
+readUptime :: IO Float
+readUptime = do
+  tv <- sysctlPeek "kern.boottime" :: IO TimeVal
+  nowSec <- now
+  return $ fromInteger $ toInteger $ (nowSec - (secInt $ sec tv))
+  where
+    secInt :: CTime -> Int64
+    secInt (CTime cSec) = cSec
diff --git a/src/Xmobar/Plugins/Monitors/Uptime/Linux.hs b/src/Xmobar/Plugins/Monitors/Uptime/Linux.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/Monitors/Uptime/Linux.hs
@@ -0,0 +1,24 @@
+------------------------------------------------------------------------------
+-- |
+-- Module      : Plugins.Monitors.Uptime.Linux
+-- Copyright   : (c) 2010 Jose Antonio Ortega Ruiz
+-- License     : BSD3-style (see LICENSE)
+--
+-- Maintainer  : jao@gnu.org
+-- Stability   : unstable
+-- Portability : unportable
+-- Created: Sun Dec 12, 2010 20:26
+--
+--
+-- Uptime
+--
+------------------------------------------------------------------------------
+
+
+module Xmobar.Plugins.Monitors.Uptime.Linux (readUptime) where
+
+import qualified Data.ByteString.Lazy.Char8 as B
+
+readUptime :: IO Float
+readUptime =
+  fmap (read . B.unpack . head . B.words) (B.readFile "/proc/uptime")
diff --git a/src/Xmobar/Plugins/QueueReader.hs b/src/Xmobar/Plugins/QueueReader.hs
new file mode 100644
--- /dev/null
+++ b/src/Xmobar/Plugins/QueueReader.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE RecordWildCards #-}
+module Xmobar.Plugins.QueueReader
+  (QueueReader (..)
+  ) where
+
+import Xmobar.Run.Exec (Exec (..))
+
+import Control.Monad (forever)
+import qualified Control.Concurrent.STM as STM
+
+-- | A 'QueueReader' displays data from an 'TQueue a' where
+-- the data items 'a' are rendered by a user supplied function.
+--
+-- Like the 'HandleReader' plugin this is only useful if you are
+-- running @xmobar@ from other Haskell code.  You should create a
+-- new @TQueue a@ and pass it to this plugin.
+--
+-- @
+-- main :: IO
+-- main = do
+--   q <- STM.newQueueIO @String
+--   bar <- forkIO $ xmobar conf
+--     { commands = Run (QueueReader q id "Queue") : commands conf }
+--   STM.atomically $ STM.writeTQueue q "Some Message"
+-- @
+data QueueReader a
+  = QueueReader
+  { qQueue    :: STM.TQueue a
+  , qShowItem :: a -> String
+  , qName :: String
+  }
+
+-- | This cannot be read back.
+instance Show (QueueReader a) where
+  -- | Only show the name/alias for the queue reader.
+  show q = "QueueReader " <> qName q
+
+-- | WARNING: This read instance will throw an exception if used! It is
+-- only implemented, because it is required by 'Xmobar.Run` in 'Xmobar.commands'.
+instance Read (QueueReader a) where
+  -- | Throws an 'error'!
+  readsPrec = error "QueueReader: instance is a stub"
+
+-- | Async queue/channel reading.
+instance Exec (QueueReader a) where
+  -- | Read from queue as data arrives.
+  start QueueReader{..} cb =
+    forever (STM.atomically (qShowItem <$> STM.readTQueue qQueue) >>= cb)
+
+  alias = qName
diff --git a/src/Xmobar/X11/Actions.hs b/src/Xmobar/X11/Actions.hs
--- a/src/Xmobar/X11/Actions.hs
+++ b/src/Xmobar/X11/Actions.hs
@@ -18,7 +18,7 @@
 import Graphics.X11.Types (Button)
 
 data Action = Spawn [Button] String
-                deriving (Eq)
+                deriving (Eq, Show)
 
 runAction :: Action -> IO ()
 runAction (Spawn _ s) = void $ system (s ++ "&")
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
@@ -60,6 +60,7 @@
       getWidth (Text s,cl,i,_) =
         textWidth d (safeIndex fs i) s >>= \tw -> return (Text s,cl,i,fi tw)
       getWidth (Icon s,cl,i,_) = return (Icon s,cl,i,fi $ iconW s)
+      getWidth (Hspace p,cl,i,_) = return (Hspace p,cl,i,fi p)
 
   p <- liftIO $ createPixmap d w wid ht
                          (defaultDepthOfScreen (defaultScreenOfDisplay d))
@@ -102,6 +103,7 @@
 verticalOffset ht (Icon _) _ _ conf
   | iconOffset conf > -1 = return $ fi (iconOffset conf)
   | 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 ()
@@ -160,6 +162,7 @@
     (Icon p) -> liftIO $ maybe (return ())
                            (B.drawBitmap d dr gc fc bc offset valign)
                            (lookup p (iconS r))
+    (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)
diff --git a/src/Xmobar/X11/Parsers.hs b/src/Xmobar/X11/Parsers.hs
--- a/src/Xmobar/X11/Parsers.hs
+++ b/src/Xmobar/X11/Parsers.hs
@@ -28,11 +28,11 @@
 import Graphics.X11.Types (Button)
 import Foreign.C.Types (CInt)
 
-data Widget = Icon String | Text String
+data Widget = Icon String | Text String | Hspace Int32 deriving Show
 
-data BoxOffset = BoxOffset Align Int32 deriving Eq 
+data BoxOffset = BoxOffset Align Int32 deriving (Eq, Show)
 -- margins: Top, Right, Bottom, Left
-data BoxMargins = BoxMargins Int32 Int32 Int32 Int32 deriving Eq
+data BoxMargins = BoxMargins Int32 Int32 Int32 Int32 deriving (Eq, Show)
 data BoxBorder = BBTop
                | BBBottom
                | BBVBoth
@@ -40,14 +40,14 @@
                | BBRight
                | BBHBoth
                | BBFull
-                 deriving ( Read, Eq )
-data Box = Box BoxBorder BoxOffset CInt String BoxMargins deriving Eq
+                 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
@@ -68,6 +68,7 @@
            -> 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)
@@ -91,6 +92,7 @@
                                      try (string "action=") <|>
                                      try (string "/action>") <|>
                                      try (string "icon=") <|>
+                                     try (string "hspace=") <|>
                                      try (string "raw=") <|>
                                      try (string "/fn>") <|>
                                      try (string "/box>") <|>
@@ -132,6 +134,13 @@
   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])]
diff --git a/xmobar.cabal b/xmobar.cabal
--- a/xmobar.cabal
+++ b/xmobar.cabal
@@ -1,5 +1,5 @@
 name:               xmobar
-version:            0.39
+version:            0.40
 homepage:           http://xmobar.org
 synopsis:           A Minimalistic Text Based Status Bar
 description: 	    Xmobar is a minimalistic text based status bar.
@@ -140,6 +140,7 @@
                    Xmobar.Plugins.Date,
                    Xmobar.Plugins.EWMH,
                    Xmobar.Plugins.HandleReader,
+                   Xmobar.Plugins.QueueReader,
                    Xmobar.Plugins.PipeReader,
                    Xmobar.Plugins.MarqueePipeReader,
                    Xmobar.Plugins.StdinReader,
@@ -149,21 +150,25 @@
                    Xmobar.Plugins.NotmuchMail,
                    Xmobar.Plugins.Monitors,
                    Xmobar.Plugins.Monitors.Batt,
+                   Xmobar.Plugins.Monitors.Batt.Common,
                    Xmobar.Plugins.Monitors.Common.Output,
                    Xmobar.Plugins.Monitors.Common.Parsers,
                    Xmobar.Plugins.Monitors.Common.Files,
                    Xmobar.Plugins.Monitors.CoreTemp,
                    Xmobar.Plugins.Monitors.K10Temp,
+                   Xmobar.Plugins.Monitors.Cpu.Common,
                    Xmobar.Plugins.Monitors.CpuFreq,
                    Xmobar.Plugins.Monitors.Disk,
                    Xmobar.Plugins.Monitors.Mem,
                    Xmobar.Plugins.Monitors.MultiCoreTemp,
                    Xmobar.Plugins.Monitors.MultiCpu,
                    Xmobar.Plugins.Monitors.Net,
+                   Xmobar.Plugins.Monitors.Net.Common,
                    Xmobar.Plugins.Monitors.Swap,
                    Xmobar.Plugins.Monitors.Thermal,
                    Xmobar.Plugins.Monitors.ThermalZone,
                    Xmobar.Plugins.Monitors.Top,
+                   Xmobar.Plugins.Monitors.Top.Common,
                    Xmobar.Plugins.Monitors.Uptime,
                    Xmobar.Plugins.Monitors.Bright,
                    Xmobar.Plugins.Monitors.CatInt
@@ -290,8 +295,24 @@
 
     if os(freebsd)
        -- enables freebsd specific code
+       extra-libraries: procstat
+                      , kvm
        build-depends: bsd-sysctl
-       cpp-options: -DFREEBSD
+       other-modules: Xmobar.Plugins.Monitors.Batt.FreeBSD,
+                      Xmobar.Plugins.Monitors.Cpu.FreeBSD,
+                      Xmobar.Plugins.Monitors.Mem.FreeBSD,
+                      Xmobar.Plugins.Monitors.Net.FreeBSD,
+                      Xmobar.Plugins.Monitors.Swap.FreeBSD,
+                      Xmobar.Plugins.Monitors.Top.FreeBSD,
+                      Xmobar.Plugins.Monitors.Uptime.FreeBSD
+    else
+       other-modules: Xmobar.Plugins.Monitors.Batt.Linux,
+                      Xmobar.Plugins.Monitors.Cpu.Linux,
+                      Xmobar.Plugins.Monitors.Mem.Linux,
+                      Xmobar.Plugins.Monitors.Net.Linux,
+                      Xmobar.Plugins.Monitors.Swap.Linux,
+                      Xmobar.Plugins.Monitors.Top.Linux,
+                      Xmobar.Plugins.Monitors.Uptime.Linux
 
 executable xmobar
     default-language:   Haskell2010
@@ -352,6 +373,7 @@
                  Xmobar.Plugins.Monitors.Common.Output
                  Xmobar.Plugins.Monitors.Common.Files
                  Xmobar.Plugins.Monitors.Cpu
+                 Xmobar.Plugins.Monitors.Cpu.Common
                  Xmobar.Plugins.Monitors.CpuSpec
                  Xmobar.Plugins.Monitors.Common.Run
                  Xmobar.Run.Exec
@@ -367,6 +389,13 @@
                      Xmobar.Plugins.Monitors.AlsaSpec
 
       cpp-options: -DALSA
+
+  if os(freebsd)
+       -- enables freebsd specific code
+      build-depends: bsd-sysctl
+      other-modules: Xmobar.Plugins.Monitors.Cpu.FreeBSD
+  else
+      other-modules: Xmobar.Plugins.Monitors.Cpu.Linux
 
 benchmark xmobarbench
   type: exitcode-stdio-1.0
