#+title: Using Haskell

* Writing your own xmobar in Haskell
  :PROPERTIES:
  :CUSTOM_ID: xmobar-in-haskell
  :END:

  Besides an standalone program, ~xmobar~ is also a Haskell library providing
  an interface to write your own status bar. You can write, instead of a
  configuration file, a real Haskell program that will be compiled and run
  when you invoke =xmobar=.

  Make sure that ~ghc~ will be able to locate the xmobar library, e.g. with

  #+begin_src shell
    cabal install --lib xmobar
  #+end_src

  and then write your Haskell configuration and main function using the
  functions and types exported in the library, which closely resemble those
  used in configuration files.  Here's a small example:

  #+begin_src haskell
    import Xmobar

    config :: Config
    config =
      defaultConfig
        { font = "DejaVu Sans Mono 9",
          allDesktops = True,
          alpha = 200,
          commands =
            [ Run XMonadLog,
              Run $ Memory ["t", "Mem: <usedratio>%"] 10,
              Run $ Kbd [],
              Run $ Date "%a %_d %b %Y <fc=#ee9a00>%H:%M:%S</fc>" "date" 10
            ],
          template = "%XMonadLog% }{ %kbd% | %date% | %memory%",
          alignSep = "}{"
        }

    main :: IO ()
    main = xmobar config  -- or: configFromArgs config >>= xmobar
  #+end_src

  You can then for instance run =ghc --make xmobar.hs= to create a new xmobar
  executable running exactly the monitors defined above.  Or put your
  =xmobar.hs= program in =~/.config/xmobar/xmobar.hs= and, when running the
  system-wide xmobar, it will notice that you have your own implementation
  and (re)compile and run it as needed.

* Using dynamic linking

  Setting the =with_shared= flag, which is off by default, enables (re)building
  of xmobar with shared libraries. By default, =xmobar= rebuilds itself with
  static linking, as is the common practice with Haskell programs, but some
  distributions use dynamic linking in their packages, or you might also be so
  inclined. If so, this is your flag.

* Writing a plugin
  :PROPERTIES:
  :CUSTOM_ID: writing-a-plugin
  :END:
  Writing a plugin for xmobar is very simple!

  First, you need to create a data type with at least one constructor.  Next
  you must declare this data type an instance of the =Exec= class, by defining
  the one needed method (alternatively =start= or =run=) and 3 optional ones
  (=alias=, =rate=, and =trigger=):

  #+begin_src haskell
    start   :: e -> (String -> IO ()) -> IO ()
    run     :: e -> IO String
    rate    :: e -> Int
    alias   :: e -> String
    trigger :: e -> (Maybe SignalType -> IO ()) -> IO ()
  #+end_src

  =start= must receive a callback to be used to display the =String= produced by
  the plugin. This method can be used for plugins that need to perform
  asynchronous actions. See =src/Xmobar/Plugins/PipeReader.hs= for an example.

  =run= can be used for simpler plugins. If you define only =run= the plugin
  will be run every second. To overwrite this default you just need to
  implement =rate=, which must return the number of tenth of seconds between
  every successive runs. See [[https://codeberg.org/xmobar/xmobar/src/branch/master/etc/xmobar.hs][etc/xmobar.hs]] for an example of a plugin
  that runs just once, and [[https://codeberg.org/xmobar/xmobar/src/branch/master/src/Xmobar/Plugins/Date.hs][src/Xmobar/Plugins/Date.hs]] for one that
  implements =rate=.

  Notice that Date could be implemented as:

  #+begin_src haskell
    instance Exec Date where
        alias (Date _ a _) = a
        start (Date f _ r) = date f r

    date :: String -> Int -> (String -> IO ()) -> IO ()
    date format r callback = do go
        where go = do
                t <- toCalendarTime =<< getClockTime
                callback $ formatCalendarTime defaultTimeLocale format t
                tenthSeconds r >> go
  #+end_src

  Modulo some technicalities like refreshing the time-zone in a clever way,
  this implementation is equivalent to the one you can read in
  =Plugins/Date.hs=.

  =alias= is the name to be used in the output template. Default alias will be
  the data type constructor.

  After that your type constructor can be used as an argument for the
  Runnable type constructor =Run= in the =commands= list of the configuration
  options.

  If your plugin only implements =alias= and =start=, then it is advisable to
  put it into the =Xmobar/Plugins/Monitors= directory and use one of the many
  =run*= functions in [[https://codeberg.org/xmobar/xmobar/src/branch/master/src/Xmobar/Plugins/Monitors/Common/Run.hs][Xmobar.Plugins.Monitors.Run]] in order to define
  =start=. The =Exec= instance should then live in [[https://codeberg.org/xmobar/xmobar/src/branch/master/src/Xmobar/Plugins/Monitors.hs][Xmobar.Plugins.Monitors]].

* Using a plugin

  To use your new plugin, you just need to use a pure Haskell configuration
  for xmobar (as explained [[#xmobar-in-haskell][above]]) and load your definitions in your =xmobar.hs=
  file. You can see an example in [[https://codeberg.org/xmobar/xmobar/src/branch/master/etc/xmobar.hs][etc/xmobar.hs]] showing you how to write
  a Haskell configuration that uses a new plugin, all in one file.

  When xmobar runs with the full path to that Haskell file as its argument
  (or if you put it in =~/.config/xmobar/xmobar.hs=), and with the xmobar
  library installed (e.g., with =cabal install --lib xmobar=), the Haskell
  code will be compiled as needed, and the new executable spawned for you.

  That's it!

* Further links

  For an elaborated, experimental and underdocumented example of writing your
  own repos and status bars using xmobar, see [[https://codeberg.org/jao/xmobar-config][this repo at jao/xmobar-config]].
