Home

xmonad

Using Notify-OSD for XMonad Notifications

In my continuing efforts to strip my computing experience of any non-essential parts, I’ve decided to ditch my statusbars. My desktop is now solely a grid of tiled terminals (and a browser). It’s quite nice. The only thing I slightly missed, however, was notifications when one of my windows set Urgency. This used to trigger a bright yellow color for that workspace in my dzen-based statusbar.

A Brief Tangent:

Windows have these properties called “hints” which they can set on themselves at will. These properties can be read by Window Managers in an effort to do the Right Thing. Hints are how a Window tells the Manager, “Hey, I should be full-screen” or, “I’m a dialog, float me on top of everything”. One such hint is WM_URGENT.

WM_URGENT is how windows get your attention. It’s what makes them flash in your task bar or bounce in your dock. If you’re using a sane terminal, it should set WM_URGENT on itself if the program running within it triggers a “bell”.

By telling applications like mutt or weechat to print a bell when I get new email or someone nick-highlights me, I can easily get notifications of these events even from applications that are running within screen, in an ssh session, on some server far, far away. Pretty neat.

Now that I’m without a status bar, I need to be notified some other way. Enter Notify-OSD.

Notify-OSD

Notify-OSD is part of the desktop notification system of GNOME, but it can be installed standalone and used to send notifications from the command-line very easily:

$ notify-send "A title" "A message"

So how do we get XMonad to send a useful notification via notify-send whenever a window sets the WM_URGENT hint? Enter the UrgencyHook.

UrgencyHook

Setting a custom urgency hook is very easy, but not exactly intuitive. What we’re actually doing is declaring a custom data type, then making it an instance of the UrgencyHook typeclass. The single required function to be a member of this typeclass is an action which will be run whenever a window sets urgency. Conveniently, it’s given the window with urgency as an argument. We can use this to format our notification.

First off, add the module imports we’ll need:

import XMonad.Hooks.UrgencyHook
import XMonad.Util.NamedWindows
import XMonad.Util.Run

import qualified XMonad.StackSet as W

Then make that custom datatype and instance:

data LibNotifyUrgencyHook = LibNotifyUrgencyHook deriving (Read, Show)

instance UrgencyHook LibNotifyUrgencyHook where
    urgencyHook LibNotifyUrgencyHook w = do
        name     <- getName w
        Just idx <- fmap (W.findTag w) $ gets windowset

        safeSpawn "notify-send" [show name, "workspace " ++ idx]

Finally, update main like so:

main :: IO ()
main = xmonad
     $ withUrgencyHook LibNotifyUrgencyHook
     $ defaultConfig
        { -- ...
        , -- ...
        }

To test this, open a terminal in some workspace and type:

$ sleep 3 && printf "\a"

Then immediately focus away from that workspace. In a few seconds, you should see a nice pop-up like:

notify-send 

You can see the title of the notification is the window name and I use the message to tell me the workspace number. In this case, the name is the default “urxvt” for a terminal window, but I also use a few wrapper scripts to open urxvt with the -n option to set its name to something specific which will then come through in any notifications from that window.

If that doesn’t work, it’s likely your terminal doesn’t set Urgency on bells. For rxvt at least, the setting is:

URxvt*urgentOnBell: true
URxvt*visualBell:   false

In Xresources or Xdefaults, whichever you use.

15 Oct 2013, tagged with xmonad, haskell, notify-osd

Lazy Haskell

Let’s say that you have a list of values and you needed to check if any of those values satisfied some condition.

This can be solved easily with Haskell’s any function, but let’s say you didn’t have that.

Here’s an alternative method using foldr.

any' :: (a -> Bool) -> [a] -> Bool
any' p list = foldr ((||) . p) False list

any' (== 'x') ['x','y','z']
-- True

any' even [1,3,5,7]
-- False

To understand how this works, you’ve got to wrap your head around two non-trivial functions for haskell beginners: foldr and ..

Dot

. takes two functions and composes them to create a new function. This is best seen by way of example.

If you know that length counts items in a list and even tests if a number is even, then when someone asks you to write a function that determines if the number of characters in a string is even, it should be little more than these two words put together some way…

-- types refresher:
-- 
-- String = [Char]
-- length :: [a] -> Int
-- even   ::        Int -> Bool
-- 

-- correct, but ugly
stringIsEven :: String -> Bool
stringIsEven s = even (length s)

-- better, but too long
stringIsEven s = even $ length s

-- perfect
stringIsEven = even . length

Remember, $ is function application while . is function composition.

I think that gives you a general idea for how it works. Now let’s translate that to the specific example at hand:

(||) is just another function. Think about that for a minute. Welcome to haskell.

(||) takes two Bools and returns a Bool; True if either one of its arguments is True.

(||) True False
-- True

(||) False False
-- False

Curious how (||) is defined in haskell’s Prelude?

(||) :: Bool -> Bool -> Bool
True  || _ = True
False || x = x

Wow.

Haskell’s laziness means there’s no special tricks needed to make if statements “short circuit”. Haskell won’t evaluate the second expression if the first is True because it’s simply never needed.

OK, back to our function.

p is provided as the first argument to our any' function and we know that it’s type is (a -> Bool). This means it has to be a test that will check a value and return True or False.

So, what might the type of ((||) . p) be?

This composed function (and you’ve really got to think of it as one function) will take some value, a as its first argument. It will apply p to it which gives an intermediate Bool. That Bool is then passed through as the first argument to (||).

(||), having gotten its first argument already, only needs one more argument. Since it’s not supplied by anything else, it’s now required as an argument to the composed function.

-- suppose p is already defined like so:
p :: Char -> Bool
p = (== 'z')

-- types refresher:
-- 
-- p    :: Char -> Bool
-- (||) ::         Bool -> Bool -> Bool
-- 

((||) . p) 'z' False
-- True

((||) . p) 'x' True
-- True


-- ((||) . p) :: Char -> Bool -> Bool

Easy, right?

Folds

The next crazy function is foldr. A fold in the general sense is a way to reduce a list.

If you’ve got a list of items, a reducing function, and some initial value, then a fold is the process of using these three things to reduce the list to a single value.

This can be seen in the type of foldr. A great deal of information can be learned in haskell by simply taking a look at types; that’s why haddocks are so invaluable.

foldr :: (a -> b -> b) -- ^ a reducing function
      -> b             -- ^ some initial value
      -> [a]           -- ^ a list of items
      -> b             -- ^ the resultant single value

Take care to note the type of the reducing function.

It must accept as its first argument the same type as your list of items contains and as its second argument the same type as your initial value.

Its result is also the same type as your initial value. This is important because the initial value and the result of the previous application of foldr must be the same type if we want the required recursion to be type safe.

Often, the types a and b are the same (as in sum' explained below), but this is not required.

foldr and foldl are different in the direction of the fold: folding to the right or folding to the left. In some cases this doesn’t matter, in others it does.

Let’s look at a folding sum as a concrete example:

sum' :: [Int] -> Int
sum' xs = foldr (+) 0 xs

sum' [1,2,3,4,5]
-- 15

-- how's it work?
foldr (+) 0 [1,2,3,4,5]
-- 15

-- the reducing function is applied with its second argument as the 
-- initial value
result     = (+) 1 0  -- 1 + 0  = 1

-- now we apply the same function but use the result of the previous 
-- application as the new initial value and act on the next element
result'    = (+) 2 1  -- 2 + 1  = 3

-- rinse and repeat until all elements are used up
result''   = (+) 3 3  -- 3 + 3  = 6
result'''  = (+) 4 6  -- 4 + 6  = 10
result'''' = (+) 5 10 -- 5 + 10 = 15

((((0 + 1) + 2) + 3) + 4) + 5 
-- 15

Here’s another breakdown with the recursion explicitly shown rather than the values it represents:

foldr (+) 0 [1,2,3,4,5]

result     =                                 (+) 1 0
result'    =                         (+) 2 $ (+) 1 0
result''   =                 (+) 3 $ (+) 2 $ (+) 1 0
result'''  =         (+) 4 $ (+) 3 $ (+) 2 $ (+) 1 0
result'''' = (+) 5 $ (+) 4 $ (+) 3 $ (+) 2 $ (+) 1 0
-- 15

This is an easy example where you can see clearly how things work out. In our case it’s a bit more complex, but the principle is the same:

-- assume p is defined like so
p :: Char -> Bool
p = (== 'b') 


foldr ((||) . p) False ['a','b','c']
-- True

-- value breakdown:
result   = ((||) . p) 'a' False -- (== 'b') 'a' || False = False
result'  = ((||) . p) 'b' False -- (== 'b') 'b' || False = True   DING!
result'' = ((||) . p) 'c' True  -- (== 'b') 'c' || True  = True

-- recursion breakdown:
result    =                                   ((||) . p) 'a' False
result''  =                  ((||) . p) 'b' $ ((||) . p) 'a' False -- <- this is the only
result''' = ((||) . p) 'c' $ ((||) . p) 'b' $ ((||) . p) 'a' False --    line ever evaulated
-- True

So the whole thing reduces to True, just as we’d expect.

Why?

This was a really slow and deliberate explanation. I did it this way because I had a real-world situation where I had to come to this exact understanding to solve a problem. OK, not really to solve some dire problem per say, but to do something I wanted to do in an elegant way…

I wanted to walk you all through it from the start only so someone not-so-familiar with haskell might a) see its beauty and b) actually understand the single line of code I’m going to show you in a few more paragraphs.

Sorry.

In my window manager of choice, XMonad, there is a means to test a window’s property and take some action depending on the result.

The simplest example is move windows with class “firefox” to the “web” workspace.

className =? "firefox" --> doShift "web"

Easy.

There’s also a means to OR rules like these together.

With this, I can say move windows with class “firefox” OR title “chrome” to the “web” workspace.

className =? "firefox" <||> title =? "chrome" --> doShift "web"

The two functions (=?) and (<||>) behave exactly like their normal (==) and (||) counterparts. They’re just lifted into a Query Monad. This is a concept you don’t need to comprehend right now, just know that there’s no elegant way to apply any in this context.

That made it difficult to write a simple function: matchAny that could be a test if any of a window’s properties (class, title, name, or role) matched the test string.

Now the any' exercise isn’t looking so unrealistic, is it?

-- types refresher:
-- 
-- any :: (a -> Bool) -> [a] -> Bool
-- 
-- we need the same thing, just lifted to the "Query" context:
-- liftAny :: (a -> Query Bool) -> [a] -> Query Bool
-- 

-- our any reimplimentation from ealier:
any'    p list = foldr ((||)   . p)         False  list

-- almost identical:
liftAny p list = foldr ((<||>) . p) (return False) list

return False is simply the lifted version of False just like (=?) is the lifted version of (==)

Now my manage hooks can leverage a list comprehension for a much more concise and readable rule.

matchAny :: String -> Query Bool
matchAny s = liftAny (=? s) [className, title, name, role]

myManageHook = composeAll [ matchAny s --> action | (s, action) <- myActions ]

    where

        myActions = [ ("rdesktop"  , doFloat         )
                    , ("Xmessage"  , doCenterFloat   )
                    , ("Gmrun"     , doCenterFloat   )
                    , ("Uzbl"      , doShift "2-web" )
                    , ("Uzbl-core" , doShift "2-web" )
                    , ("Chromium"  , doShift "2-web" )
                    , ("irssi"     , doShift "3-chat")
                    ]

Finally

So why is this post about laziness?

foldr ((||) . (== True)) False [False, False, True, undefined, undefined]
-- True

That statement “short circuits”. That’s only possible because of lazy evaluation.

09 Apr 2011, tagged with haskell, xmonad

XMonad Modules

This page is to serve as both an apology and an announcement. I’ve recently modularized my xmonad.hs. I’m sorry.

This is no longer true. I’ve since gone through a bit of a config cleanse, deciding it makes my life easier to live closer to defaults and not carry around a lot of extra configuration or features (that I don’t actively use).

As part of this cleanse, I’ve stripped my config back down to a very lean xmonad.hs that can easily live within the confines of a single file.

Who cares?

I know of at least one person who stops by my site on a regular basis to update his xmonad.hs to match the latest version of mine. I’ve also seen, on a few occasions, someone mention that they use brisbin33’s xmonad config when discussing an issue on the forums or in IRC. True, for all I know, there could be only three people using some form of my config – but to them, I’m sorry.

Anyone who blindly updates to my most recent xmonad.hs may get hit with the following error:

  xmonad.hs:21:7:
      Could not find module `ScratchPadKeys':
         Use -v to see a list of the files searched for.

  Failed, modules loaded: none.

That’s because I’ve offloaded some of the more module-ish chunks of my config into, well, modules.

Why?

I noticed, when browsing the XMonad source (I know, shut-up), that the default recompile command includes the option -ilib this tells ghc to include source files in ./lib. It was a light-bulb moment.

I had gathered some pretty sophisticated code in my little xmonad.hs: custom data types and instances, reusable utilities, etc. Why not put them in their own files and import them into a nice clean config as I would with any normal contrib module?

So, if you’re following my xmonad.hs, please continue to do so. Just be advised you’ll need a few files in lib if you want to use the functionality they offer.

31 Aug 2010, tagged with dzen, haskell, website, xmonad

Haskell RSS Reader

I’ve been looking for a good Haskell project for a while now. The language is just awesome, and I’ve been getting more and more comfortable with it lately thanks to reading Real World Haskell. I even got the opportunity to write some haskell for a project at work (I’m a consultant on a Microsoft product, crazy).

I wanted something challenging but doable; something to keep me interested but still stretch my abilities. I had made some smaller utilities to manage the pages on my site, so I was getting familiar with parsing XML using some haskell libraries as well as starting to wrap my head around the IO Monad a bit more. Well, I just completed (what I think is) a slick little RSS reader using just haskell and dzen.

For those that don’t know, RSS feeds are basically just site headlines; a very simple XML page that lists items, each item containing a title, description, and link.

So my reader would read in a listing of feed urls, put together all of the RSS items from each url, and then display them using dzen.

I put it in the upper right of my left monitor, configured to look like part of my existing dzen status bars.

The title text remains static and is clickable (opens the url of the feed item), and the description text is a ticker text that rolls by right-to-left one character at a time.

Installation

First, you would have to download RssReader.hs and Dzen.hs from my old xmonad library and place them in a directory along side a file called rssreader.hs. This file would serve the same purpose xmonad.hs does for XMonad: it would be both a configuration file and the main application itself, gluing together imported functions into a runnable main.

Here’s an example:

import Dzen
import RssReader

-- 
-- this is it, the whole application in one line!
-- 

main :: IO ()
main = spawnDzen dzenConf >>= spawnReader readerConf

-- 
-- and the configuration part...
-- 

-- set a width and some text formatting
readerConf :: ReaderConf
readerConf = defaultReaderConf
  { titleFormat = dzenFG "#909090"
  , descrFormat = shorten 200 
  , tickerWidth = 150 
  }

  where
    -- some helpers
    dzenFG c s  = concat ["^fg(", c, ")", s, "^fg()"]
    shorten n s = if length s > n then (take n s) ++ "..." else s

-- start with the default dzen and override some things
dzenConf :: DzenConf
dzenConf = defaultDzen
  { x_position  = Just $ Percent 60 -- start 60% across screen 0
  , width       = Just $ Percent 40 -- and span the other 40%
  , font        = Just "Verdana-8"  -- if you have an xft-capable dzen
  , fg_color    = Just "#606060"
  , bg_color    = Just "#303030"
  }

Once that’s all set, you can run ghc --make -o rssreader rssreader.hs inside this directory to create an executable which you can run standalone.

Dependencies

The following packages would be required either from Hackage or your distribution’s package manager:

Hackage Arch linux
http extra/haskell-http
tagsoup aur/haskell-tagsoup

Known Issues

Some unprintable characters seem to still come through. I try to clean the strings as much as possible, but I still see boxes in dzen from time to time.

The rssreader and the spawned dzen are not tied together process-wise. This means that you can kill rssreader and a frozen dzen remains, or you can quit the dzen and rssreader will be left as a zombie.

15 Aug 2010, tagged with dzen, haskell, xmonad

Scratchpad Everything

If you’ve read my recent post on using a scratchpad in XMonad, and if you’ve actually implemented this in your own setup, you probably know how useful it is. For those that don’t know what I’m talking about, you basically setup a simple keybinding that calls up a terminal (usually floated, but managed by its own specific manageHook) to be used briefly before being banished away by the same keybinding.

Recently, I found that you can apply this functionality to any application you’d like.

ScratchMixer

I have my music playing through MPD all the time. Occasionally, I’ll like to play some other media, a youtube video or what have you. When I do this, I call up ossxmix, adjust down MPD, and adjust up my browser (per application volumes are awesome by the way).

I realized that this was a perfect scratchpad scenario. I was calling up this application for just a second, using it, then sending it away. This simple activity was requiring that I M-p, type ossxmix, hit enter, layout-shuffle, adjust volumes, then M-S-c every single time. What was I thinking?

XMonad.Util.NamedScratchpad

My last writeup used the contrib module XMonad.Util.Scratchpad which, though it has a shorter name, simply provided wrapper functions for the things I’m now using from XMonad.Util.NamedScratchpad.

In the parent extension, things are much more transparent and free. For me, this lead to a much cleaner config file too. I wish I had been using things this way from the start.

So of course, we’ll need to add import XMonad.Util.NamedScratchpad to the top of our config file.

Please refer back to my previous post for information regarding some boilerplate code. This writeup assumes you have a main-do block that calls out myManageHook and myKeys to be defined as separate functions. I also won’t be going into hiding the NSP

Scratchpads

The Named Scratchpad extension exposes a new data type that can be used to represent a scratchpad. The following four things must be specified to fully describe a scratchpad:

  • A String: the name to call it
  • A String: the command to launch
  • A Query Bool: The way to find the window once it’s running
  • A ManageHook: The way to manage the window when we call it up

Those last two data types might sound scary, but they aren’t. If you think of the fact that most users define custom window management in a list of (Query Bool --> ManageHook) and one representation of this might be (className =? "Firefox" --> doFloat) that should give you an idea of the sorts of functions that you should use to fill those last two slots for your scratchpads.

The haddocks for this module talk about everything that’s available, but here’s a commented version of my declaration:

myScratchPads = [ NS "mixer"    spawnMixer findMixer manageMixer -- one scratchpad
                , NS "terminal" spawnTerm  findTerm  manageTerm  -- and a second
                ]

  where

    spawnMixer  = "ossxmix"                               -- launch my mixer
    findMixer   = className =? "Ossxmix"                  -- its window has a ClassName of "Ossxmix"
    manageMixer = customFloating $ W.RationalRect l t w h -- and I'd like it fixed using the geometry below:

      where

        h = 0.6       -- height, 60% 
        w = 0.6       -- width, 60% 
        t = (1 - h)/2 -- centered top/bottom
        l = (1 - w)/2 -- centered left/right

    spawnTerm  = myTerminal ++ " -name scratchpad"       -- launch my terminal
    findTerm   = resource  =? "scratchpad"               -- its window will be named "scratchpad" (see above)
    manageTerm = customFloating $ W.RationalRect l t w h -- and I'd like it fixed using the geometry below

      where

        -- reusing these variables is ok since they're confined to their own 
        -- where clauses 
        h = 0.1       -- height, 10% 
        w = 1         -- width, 100%
        t = 1 - h     -- bottom edge
        l = (1 - w)/2 -- centered left/right

So you can see I have a list containing two scratchpads. The datatype syntax requires the “NS” plus the four things I’ve listed above.

You’ll notice I liberally use sub-functions via where clauses. You can think of these as simple variables and if parenthesized and placed directly where they’re called out, they would work exactly the same. I think this is clearer and it should be fairly obvious how it works.

The beauty of all this is that it’s almost all that’s needed. Each scratchpad has a name which can be bound to a key; even better, the whole scratchpad list will be managed with one simple addition to your manageHook.

I inserted the following keybindings:

myKeys = [ ...
         , ...

         , ("M4-t"   , scratchTerm )
         , ("M4-S-m" , scratchMixer)

         , ...
         ] 

         where

           -- this simply means "find the scratchpad in myScratchPads that is 
           -- named terminal and launch it"
           scratchTerm  = namedScratchpadAction myScratchPads "terminal"
           scratchMixer = namedScratchpadAction myScratchPads "mixer"

I’m using EZConfig notation in my keybindings.

And tacked the following onto the end of my managehook:

myManageHook = ([ -- whatever it might be...
                , ...
                , ...

                -- this manages the entire list of scratchpads 
                -- based on the query and hook listed for each
                ]) <+> namedScratchpadManageHook myScratchPads

That’s it, a scratch terminal and a scratch mixer; but most importantly, simple and transparent tools for adding any arbitrary application (graphical or in-term) as a scratchpad application.

One final note about testing: As you’re tweaking your queries and hooks, be sure to call up the application, close it, then Mod-Q and test your changes. If you’ve got a scratchpad still open from before your last config change, it will still be using the old ManageHook.

14 Jun 2010, tagged with haskell, xmonad

XMonad Scratchpad

It’s been a while since I’ve made an XMonad post. Thought a good one might be details regarding the scratchpad extension from -contrib. This can be confusing to set up, but oh-so useful. If you’ve ever used a quake (or yakuake?) terminal (I have not), you’ll know what I’m talking about. It’s a small terminal that sits idle on a non-visible workspace. You can call it up with a quick keybind, use it for whatever, then banish it away again with the same keybind.

You just have to use it for a while to realize how useful it really is. My goal for this post is to distill out of my xmonad.hs just the scratchpad functionality so that someone with an existing xmonad.hs could easily plug this into their setup with minimal fuss.

Prerequisites

I’m going to assume that your existing xmonad.hs defines a function called myManageHook and another called myTerminal. If this is not the case, take a look at the below snippet; I think you’ll be able to figure out how to rework whatever you do have into this format.

main = do
  xmonad $ defaultConfig
    { terminal   = myTerminal
    , manageHook = myManageHook
    , ...
    , ...
    }

myTerminal = "urxvt"

-- you could have some crazy long managehook 
-- or simply defaultManageHook
myManageHook = ...

Imports

You’ll need the following import to make this functionality available. Make sure you’ve got -contrib installed and add the following to the top of your xmonad.hs:

import XMonad.Util.Scratchpad

ManageHook

We’re going to add an additional ManageHook to manage the scratch-pad specifically. In XMonad, we can compose ManageHooks together in a few ways. The easiest is probably to combine whatever you currently have with our new one via <+\>, and infix function of type ManageHook -> ManageHook -> ManageHook:

-- depending on what you have, parens may or may not be needed
myManageHook = (...) <+> manageScratchPad

-- then define your scratchpad management separately:
manageScratchPad :: ManageHook
manageScratchPad = scratchpadManageHook (W.RationalRect l t w h)

  where

    h = 0.1     -- terminal height, 10%
    w = 1       -- terminal width, 100%
    t = 1 - h   -- distance from top edge, 90%
    l = 1 - w   -- distance from left edge, 0%

What I’ve done is used RationalRect to define a rectangle of where I’d like the scratch-pad to appear. h, w, t, and l are entered as percentage screen size. So in the above, I’ve got a rectangle that spans the monitor’s entire width and is 10% its height. By specifying h and w, t and l can be derived to place it on the bottom edge of the screen.

KeyBinds

I’m not really going to get specific with the key binding part. Personally, I use EZConfig. Everyone seems to have their own syntax/style of binding keys in xmonad; usually it’s just the way it was in the first config you copied from, whatever. Just know that someway-somehow you’ll need to bind a key to…

myKeys = [ ( ... , ...        )
         , ( ... , scratchPad ) -- spawn a scratchpad terminal
         ]

         where 

           scratchPad = scratchpadSpawnActionTerminal myTerminal

Make sense?

Extra credit

At this point, you should have a functioning scratchpad. Remember, any changes to the manageHook require you to exit and reopen the scratchpad terminal to see the effect.

Using this scratchpad module creates a workspace called NSP where the scratchpad resides when it’s not visible. You’ll notice, this workspace will show up in any dzen or xmobar you’ve got going on. But with some changes to our logHook we can filter that out of the workspace list pretty easily.

If you’re not using a custom logHook, you’ve pretty much got two choices at this point: head over to the docs on xmonad.org and find some drop-in filter-out-NSP module and figure out how add it (I know it’s there but I could not for the life of me get it working), or just figure out how to get a custom logHook going.

What I’m about to go into assumes you’ve already got something like the following defined in your xmonad.hs:

myLogHook h = dynamicLogWithPP $ defaultPP
  { ppCurrent         = dzenColor color1 color2 . pad
  , ppVisible         = dzenColor color1 color2 . pad
  , ppUrgent          = dzenColor color1 color2 . pad . dzenStrip
  , ppLayout          = dzenColor color1 color2 . pad
  , ppHidden          = dzenColor color1 color2 . pad
  , ppHiddenNoWindows = namedOnly
  , ppTitle           = shorten 100 
  , ppSep             = " "
  , ppWsSep           = ""
  , ppOutput          = hPutStrLn h
  }

The above requires other contrib modules, changes to main, and special imports to get working. As I’ve said, I’m leaving it as an exercise for the reader to set up his or her own logHook.

Once we’ve got this, filtering out the NSP workspace is pretty straight forward. Here’s the above again, but this time with the NSP workspace filtered out, hopefully you’ll be able to modify things as needed to make this work with your setup.

myLogHook h = dynamicLogWithPP $ defaultPP
  { ppCurrent         = dzenColor color1 color2 . pad
  , ppVisible         = dzenColor color1 color2 . pad
  , ppUrgent          = dzenColor color1 color2 . pad . dzenStrip
  , ppLayout          = dzenColor color1 color2
  , ppLayout          = dzenColor color1 color2 . pad
  , ppHidden          = dzenColor color1 color2 . pad . noScatchPad -- haskell makes it so easy,
  , ppHiddenNoWindows = noScratchPad                                -- just tack on another function
  , ppTitle           = shorten 100 
  , ppSep             = " "
  , ppWsSep           = ""
  , ppOutput          = hPutStrLn h
  }

  where
    -- then define it down here: if the workspace is NSP then print
    -- nothing, else print it as-is
    noScratchPad ws = if ws == "NSP" then "" else ws

Good luck!

10 Apr 2010, tagged with haskell, xmonad

XMonad's IM Layout

One of my favorite modules from xmonad-contrib is the IM layout. It’s a tiling algorithm designed to handle your roster and chat windows in the best way possible. Here I’m going to outline how I set this up in my xmonad.hs.

What it looks like

Personally, I want my roster tiled in its own properly-sized location on one side, and all other IM related windows floating. I also want any of those IM windows to automatically be pushed to the IM workspace.

IM Layout Screenshot 

Here we can see that my roster is fit in its own little tile on the left, sized properly. The rest of the screen is tiled as a grid of whatever other applications I open. My chat window is always floating.

So, how would you set this up?

Imports and Main

This post assumes you’ve imported the required modules and you have a main function setup as shown:

-- imports
import XMonad.Hooks.ManageDocks
import XMonad.Hooks.ManageHelpers
import XMonad.Layout.IM
import XMonad.Layout.PerWorkspace

import qualified XMonad.StackSet as W

-- main
main = xmonad $ defaultConfig
    { ...
    -- all of our changes will take place in the myLayout and
    -- myManageHook definitions.
    , layoutHook = myLayout
    , manageHook = myManageHook
    }

The Layout Hook

Here’s a simple layoutHook that adds the IM extension on a specific workspace and has the added bonus that you can cycle between all of your “standard” layouts in the space that’s not taken up by the roster.

Also, if your IM client isn’t open, the workspace will behave like any other.

-- Layouts
myLayout = avoidStruts $ onWorkspace "3-im" imLayout $ standardLayouts

  where
    --          numMasters, resizeIncr, splitRatio
    tall = Tall 1           0.02        0.5

    -- define the list of standardLayouts
    standardLayouts = tall ||| Mirror tall ||| Full

    -- notice that withIM, which normally acts on one layout, can also 
    -- work on a list of layouts (yay recursive data types!)
    imLayout = withIM (1/10) (Role "roster") standardLayouts

I’ve defined the function standardLayouts, which we’ll use on all workspaces. Then for “3-im”, I define imLayout which uses the withIM modifier.

This really highlights what XMonad brings as a WM, something you don’t get with most other tilers; because we are using haskell as the config language, we have all kinds of native tricks at our disposal. The reason the above works is that both (someLayout) and (someLayout ||| someOtherLayout ||| yetAnotherLayout) are valid as arguments to withIM due to their identical (existential) types. If the compiler allows it, we can be pretty sure it’ll behave as we intended.

Now you should have a nice IM layout setup, go ahead and M-q, then head to workspace 3 (or whatever) and fire up your IM client

Feel free to stop here if you plan on having your IM Chat windows tiled. I think that’s kind of ugly, so I choose to have any IM-related window besides the roster floated by default. My manageHook takes care of that.

The Manage Hook

-- Manage hook
myManageHook = composeAll
    [ -- whatever you might already have, plus...

    -- move all IM windows to IM workspace
    , className =? "Gajim.py" --> doShift "3-chat"

    -- and float everything but the roster
    , classNotRole ("Gajim.py", "roster") --> doFloat
    ]

    where
        classNotRole :: (String, String) -> Query Bool
        classNotRole (c,r) = className =? c <&&> role /=? r

        role = stringProperty "WM_WINDOW_ROLE"

This will move all IM windows to the IM workspace, and float anything that’s of the IM Class but not the roster’s Role.

You can use the commandline tool xprop to find out a window’s properties for use in these manageHooks.

05 Dec 2009, tagged with haskell, xmonad

Status Bars in XMonad

One of the trickiest things for a new user of XMonad is adding a statusbar. This is mainly because xmonad’s statusbar support is so transparent and extensible, that any documentation for setting it up could be completely different from any other. Do you want a dynamicLog? A customLog? xmobar? dzen? One bar? Two?

Here I’ll outline my method. Two separate dzen2 bars aligned to look like one bar across the top of your screen. The left fed by an xmonad dynamicLogWithPP to show workspaces (with coloring and urgencyHooks), the current layout and the current window title. The right fed by conky to show music, system stats and of course the time.

Many thanks go to moljac and lifeafter2am on the Arch forums. They offered their xmonad.hs’s to me and helped get me setup this way.

What it looks like

Full desktop:

XMonad Shot 

And with an urgencyHook notification (Workspace turns a different color):

XMonad Shot Urgent 

To achieve this, we set up a dynamicLog in xmonad.hs and adjust our main function to output to this bar and also spawn our right bar as fed by conky.

Imports and the Main function

Your imports and main function will look like this:

import XMonad.Util.Run
import XMonad.Hooks.DynamicLog
import XMonad.Hooks.ManageDocks
import XMonad.Hooks.UrgencyHook

main = do
    -- spawn our left and right bars. in my case, I use two monitors,
    -- I want one bar on each, and my version of dzen supports the -xs
    -- argument for specifying on which screen to appear. if your
    -- situation is different in some way, use -w and -x to give your
    -- bars appriate width and x offsets for your needs.
    d <- spawnPipe "dzen2 -p -xs 1 -ta l -e 'onstart=lower'"

    spawn $ "conky -c ~/.xmonad/data/conky/dzen | " ++
                "dzen2 -p -xs 2 ta -r -e 'onstart=lower'"

    xmonad $ withUrgencyHook NoUrgencyHook $ defaultConfig
        { ...
        , logHook = myLogHook d

        -- having these call out to external functions makes it easier 
        -- to add the "no overlap" stuff later on. if you don't have 
        -- myLayoutHook or myManageHook, you can continue to use the 
        -- xmonad defaults by declaring them like so:
        -- 
        -- > myManageHook = manageHook defaultConfig
        -- > myLayoutHook = layoutHook defaultConfig
        -- 
        , manageHook = myManageHook
        , layoutHook = myLayoutHook
        }

Don’t worry about the things we haven’t defined yet, I’ll get to those. Also, the conky config file which I use can be found in my xmonad repo.

Your LogHook

Your logHook will setup the output of workspaces, layouts, and titles to the left dzen. You can customize the formatting, padding, shortening, etc.

Here’s a commented version of myLogHook which, hopefully, is illustrative enough to not warrant further explanation.

-- 
-- Loghook
-- 
-- note: some of these colors may differ from what's in the
-- screenshot, it changes daily
-- 
myLogHook h = dynamicLogWithPP $ defaultPP

    -- display current workspace as darkgrey on light grey (opposite of 
    -- default colors)
    { ppCurrent         = dzenColor "#303030" "#909090" . pad 

    -- display other workspaces which contain windows as a brighter grey
    , ppHidden          = dzenColor "#909090" "" . pad 

    -- display other workspaces with no windows as a normal grey
    , ppHiddenNoWindows = dzenColor "#606060" "" . pad 

    -- display the current layout as a brighter grey
    , ppLayout          = dzenColor "#909090" "" . pad 

    -- if a window on a hidden workspace needs my attention, color it so
    , ppUrgent          = dzenColor "#ff0000" "" . pad . dzenStrip

    -- shorten if it goes over 100 characters
    , ppTitle           = shorten 100

    -- no separator between workspaces
    , ppWsSep           = ""

    -- put a few spaces between each object
    , ppSep             = "  "

    -- output to the handle we were given as an argument
    , ppOutput          = hPutStrLn h
    }

No Overlap

The last thing you should do is add two little things to make sure you leave a gap for the new statusbar:

-- add avoidStruts to your layoutHook like so
myLayoutHook = avoidStruts $ {- whatever you had before... -}

-- add manageDocks to your managehook like so
myManageHook = manageDocks <+> {- whatever you had before ... -}

Happy haskelling!

05 Dec 2009, tagged with dzen, haskell, xmonad