Hello XMonad, Goodbye AwesomeWM

After using the aptly named AwesomeWM for nearly five years I have switched back to XMonad.

AwesomeWM

In 2012 I wrote a really long blog post about my journey to AwesomeWM. I never expected that I’d abandon it because has always been so great. Unfortunately I discovered by and by, that the authors do not value my time as a user. That’s fine for them, they don’t have to, but it means that I can’t keep using AwesomeWM unless I dedicate more and more time to it.

I find it vaguely interesting that while the authors of AwesomeWM claim, and indeed I even believed, that AwesomeWM is a toolkit for building window managers, end users are expected to start from a known boilerplate and modify from there. The lack of even an attempt at backwards compatibility is just too much.

Last time there was an update to awesome I ended up spending almost a whole day fixing my config. The 4.0 migration looks even bigger and I’ve done more with the 3.x series (custom widgets etc) that would have to be ported or rewritten. I decided that it would be easier for me to switch from AwesomeWM to XMonad than to migrate to AwesomeWM 4.0, and more likely to continue working in the future.

Enter XMonad

I used XMonad in the past and indeed was mostly happy with it. XMonad is great at being a tiling window manager; it almost works perfectly for me out of the box and there are mostly copy-pasteable examples on how to fix known oddities in the documentation. On the other hand, to configure XMonad

  • you have to write Haskell, which seems like a pointless difficulty when it comes to a window manager, especially after using Lua for so long
  • it has no UI support out of the box

The use of Haskell is mostly an issue for me because I am not actually interested in writing Haskell except for this purpose. I am not super interested in monads or purely functional programming; I just want to arrange some windows. On top of that, errors other than type-check errors are almost completely worthless. The following is because I didn’t have enough whitespace:

Error:
/home/frew/.config/taffybar/taffybar.hs:50:15: error:
    parse error on input ‘ebox’

For reasons that will be clear shortly I have learned a lot more Haskell than I ever did before using XMonad, so it has been less painful than last time, but it’s still frustrating.

XMonad UIs

While XMonad does not have UI support like AwesomeWM does, there are a handful of options for folks who want some anyway. In increasing features they are:

xmobar

xmobar fits firmly in with the suckless philosophy and in fact is similar to dmenu (which I use tens of times a day) from my reading. Frustratingly in its simplicity it is missing some basic features that I wouldn’t want to miss out on, so I didn’t persue it much further.

dzen2

dzen2 was the next bar I looked into. It supports a lot of features xmobar doesn’t, like basic image support, and some menu support I was considering using for mouseovers. I got fairly far with an interesting dzen2 setup that involved a multiplexor and script per widget. I really miss that setup since it was so elegant, but it was just so limiting. I may try to get back in that direction at some point because implementing these basic monitors over and over again in every langauge (Lua, Haskell, etc) is so silly.

As a side note, while I was implementing the framework above I wrote a tool to increase/decrease/mute/query the volume of my system. I am pleased because it allows toggling the mute state (of course Pulse Audio does not support that) and automatically picks the right soundcard (if I am docked I want to use my external soundcard.) I will likely resurrect this later.

Before I misrepresent, while the above framework was elegant with respect to simplicity of the widgets and how they worked, there was quite a lot of hassle involved with pipes and processes that would never die when the master process got killed. I considered switching out the pipes for simple files that would get tailed, but I moved on before I got much further.

One more interesting fact about this setup was that it did not suffer from a “slow” widget. I have spent a non-trivial amount of effort making it so that AwesomeWM would not block on an external weather service and that this new method did not have that problem out-of-the box was pretty refreshing.

taffybar

taffybar is, I think, the most advanced “bar” out there. It ships with a handful of prebuilt widgets and a few system monitors (like the scripts above, but in Haskell.) It uses GTK+ and exposes the entirety of GTK+ features to you, so if you wanted you could do something as absurd as put a little web browser into the menu bar.

The documentation is a little hard to get used to if you are not already a Haskell programmer, but there are some good examples already and the #haskell channel on freenode and the maintainers (via github issues) are incredibly helpful and friendly.

My goal all along has been to port my AwesomeWM setup and with taffybar I’ve gotten close. I have a few widgets that are graphs, have mouseovers for details, and you can click them to run some program to dig in deeper. At this point there are only two missing pieces:

  1. The temperature graph is reading from the wrong file, so is broken.
  2. There is no volume widget.

For the former I think I just need to write some Haskell like this. It’s not super difficult, but the Haskell used in that module isn’t very clear to me right now, so I either need to bang on it for a while or read it and understand it before I implement the working version.

The latter isn’t that hard either, but I’ve been spoiled by AwesomeWM. Simply rolling the mouse wheel over some random widget to volume-up/volume-down would be easy and totally useful, but adding a display of what the volume is at and if it’s muted is still really handy.

Finally, here is an example of an advanced TaffyBar widget:

import qualified Graphics.UI.Gtk as Gtk
import System.Taffybar.Widgets.PollingGraph
import System.Information.CPU
import XMonad.Util.Run

main = do
  let
    cpuReader :: Gtk.Widget -> IO [Double]
    cpuReader widget = do
      (userLoad, systemLoad, totalLoad) <- cpuLoad
      Gtk.postGUIAsync $ do
        let
          user    = round $ 100 * userLoad   :: Int
          system  = round $ 100 * systemLoad :: Int
          tooltip = printf "%02i%% User\n%02i%% System" user system :: String
        _ <- Gtk.widgetSetTooltipText widget $ Just tooltip
        return ()
      return [totalLoad, systemLoad]

    cpuButtons :: Gtk.EventM Gtk.EButton Bool
    cpuButtons = do
      e <- Gtk.eventButton
      case e of
        Gtk.LeftButton   -> unsafeSpawn "terminator -e glances"
        Gtk.RightButton  -> unsafeSpawn "terminator -e top"
        Gtk.MiddleButton -> unsafeSpawn "gnome-system-monitor"
        _ -> return ()
      return True

    cpuCfg :: GraphConfig
    cpuCfg = defaultGraphConfig { graphDataColors = [ (0, 1, 0, 1)
                                                    , (1, 0, 1, 0.5)
                                                    ]
                                }


    cpu :: IO Gtk.Widget
    cpu = do
      ebox <- Gtk.eventBoxNew
      btn <- pollingGraphNew cpuCfg 0.5 $ cpuReader $ Gtk.toWidget ebox
      Gtk.containerAdd ebox btn
      _ <- Gtk.on ebox Gtk.buttonPressEvent cpuButtons
      Gtk.widgetShowAll ebox
      return $ Gtk.toWidget ebox

In the above example, cpu would fit in with the example taffybar config as any other widget.


I hope that the TaffyBar widget example above is helpful for others; it was not trivial for me to figure out even with help from multiple avenues. To the next five years!

If you actually want to learn Haskell, Learn You a Haskell for Great Good! is pretty good. I read some a few years ago and enjoyed it.

If you are interested in inspiration configuring your window manager in a “humane” manner you might check out The Design of Everyday Things. It’s a classic; be warned though, if you read it you’ll be frustrated by many (most?) doors.

Posted Fri, May 19, 2017