Volume Control in xmonad
What you will need to follow along
- xmonad
- xmonad-contrib >= 0.10
- xmonad-extras
- dzen
The goal
import XMonad main = xmonad defaultConfig
I got very jealous of Nicole recently when we were watching some television on her Mac. The commercials came on and blasted us away... so she hit a key, and her volume magically dropped, and a beautiful graphic popped up showing the volume level. When the show came back on, another key put the volume back. Nice!
So, let's take the first step in getting xmonad to do that: let's add some keybindings for volume control and a pop-up showing the current volume level.
We'll start with the barest of bones configuration, shown to the right with a screenshot from a fictitious 320×240 monitor. I'll only put screenshots by configurations that change the visual compared to the last one.
Adding volume control
import XMonad import XMonad.Actions.Volume import Data.Map (fromList) import Data.Monoid (mappend) main = xmonad defaultConfig { keys = keys defaultConfig `mappend` \c -> fromList [ ((0, xK_F6), lowerVolume 4 >> return ()), ((0, xK_F7), raiseVolume 4 >> return ()) ] }
This is the easiest step. The XMonad.Actions.Volume
module in xmonad-extras provides a whole glut of actions for controlling the volume and muting. For now, we'll just use these two:
lowerVolume :: MonadIO m => Double -> m Double raiseVolume :: MonadIO m => Double -> m Double
The Double
arguments are how many percentage points to raise the volume; I like 4
. When you're doing this yourself, you might also want to bind a key to the toggleMute function, but I personally don't want that very often, so I decided to skip it. If you do this, you will probably also want to change lowerVolume 4
to setMute False >> lowerVolume 4
and raiseVolume 4
to setMute False >> raiseVolume 4
.
Now, my keyboard has a great big gap in between the F6 and F7 keys that make those ones super easy to find tactilely, so I chose those keys for volume adjustment (with no modmask at all). If you stare past the fluff of Haskell's record syntax, you'll see that there are really only two load-bearing lines, the ones with xK_F6
and xK_F7
. To begin with, I just threw away the return value of the volume change command.
Great, now I could control the volume with F6 and F7! I was pretty happy with this for a while, but after some testing, I decided that visual feedback really mattered. So... let's play with dzen.
Calling dzen to show the current volume
import XMonad import XMonad.Actions.Volume import XMonad.Util.Dzen import Data.Map (fromList) import Data.Monoid (mappend) alert = dzenConfig return . show main = xmonad defaultConfig { keys = keys defaultConfig `mappend` \c -> fromList [ ((0, xK_F6), lowerVolume 4 >>= alert), ((0, xK_F7), raiseVolume 4 >>= alert) ] }
It turns out there's a module for this, too, in xmonad-contrib: XMonad.Util.Dzen
. The first thing I wanted to do was make sure that dzen was working properly. That module provides the dzenConfig return :: String -> X ()
function to just throw some text up on the screen for a few seconds. So it's time to wire things up and stop throwing away the return value from the volume raise/lower actions: let's send it on to dzen instead. Of course, the returned value is a Double
, so we have to convert it to a String
first. I defined myself an alert
function that will display a representation of anything that can be show
n.
The result is kind of okay: there's a bar across the entire top of the screen for a few seconds with the new volume setting.
Prettier, pretty please
import XMonad import XMonad.Actions.Volume import XMonad.Util.Dzen import Data.Map (fromList) import Data.Monoid (mappend) alert = dzenConfig centered . show . round centered = onCurr (center 150 66) >=> font "-*-helvetica-*-r-*-*-64-*-*-*-*-*-*-*" >=> addArgs ["-fg", "#80c0ff"] >=> addArgs ["-bg", "#000040"] main = xmonad defaultConfig { keys = keys defaultConfig `mappend` \c -> fromList [ ((0, xK_F6), lowerVolume 4 >>= alert), ((0, xK_F7), raiseVolume 4 >>= alert) ] }
Okay, that works... but it's pretty ugly. I wanted to make it a little nicer:
- I only want to show the first two significant digits. After that is pretty much noise as far as I'm concerned. So I threw a
round
on the end of myalert
function. - I also wanted to center the dzen on the current screen. (My fictitious display has only one screen, but we might as well be general here.) The xmonad-contrib module provides the
center
function for this purpose. I chose the150
and66
(after some experimenting) to match the size of the rendered text; it's a hack, but it's about the best we can do without opening up some fonts within xmonad itself and checking out their metrics. - I fired up
xfontsel
and picked out a nice big font so that it would be readable. - Just for fun, I set the foreground and background colors, too.
After all that, I finally had a setup that I kind of liked. Perhaps sometime in the future, when I get some more time to hack, I'll learn about ghosd and get a truly beautiful volume indicator going. =)