Xmonad, mon nouveau window mana­ger

Une nouvelle caté­go­rie d’ar­ticles : les Dotfiles !

Pour ceux qui ne savent pas, les fichiers de confi­gu­ra­tion propres aux utili­sa­teurs sous GNU/Linux sont planqués dans leur /home sous la forme de fichiers (et réper­toires) cachés. Hors, comme il suffit de précé­der le nom du fichier par un « . » pour le cacher, on les appelle les dotfiles (dot voulant dire point en anglais).

Donc dans cette caté­go­rie, vous trou­ve­rez des fichiers de confi­gu­ra­tion pour mes appli­ca­tions que je me suis amusé à custo­mi­ser. On va commen­cer par un bon gros paquet de fichiers : mon bureau !

Mon nouvel envi­ron­ne­ment de bureau

Comme je l’ai déjà évoqué précé­dem­ment, j’ai changé d’en­vi­ron­ne­ment de bureau et me suis tourné vers Xmonad.

Il s’agit, comme Wmii et d’autres, d’un tiling window mana­ger, ce qui veut dire en gros qu’il gère la dispo­si­tion des fenêtres de manière opti­male. Lorsque j’ouvre une nouvelle fenêtre (un termi­nal, Icewea­sel, Icedove, ce que vous voulez), il ne la met pas à la dernière taille et place utili­sées mais de façon à ce que, collée aux autres fenêtres, tout l’écran soit pris. Donc plus de fenêtre dont un bout (forcé­ment impor­tant) est caché par une autre appli­ca­tion ou hors du bureau virtuel (enfin, si, on peut le faire mais il faut faire exprès).

Un petit screen­shot :

Il faut aussi dire que, de par sa légé­reté, Xmonad est très, très réac­tif !

Tout ça pour dire que je le kiffe (merci à Clém de me l’avoir fait décou­vrir).

Bon par contre, ça n’est qu’un window mana­ger, rien d’autre. Il faut donc le meubler un peu, ce que j’ai fait avec dmenu, trayer et dzen2 pour faire tenir le tout dans une barre. Les infor­ma­tions du son, de la batte­rie, etc. ne sont qu’un simple script shell pipé dans dzen2.

Le termi­nal est rxvt-unicode, plus connu sous le nom de urxvt et le logi­ciel permet­tant d’avoir un fond d’écran s’ap­pelle nitro­gen.

L’ins­tal­la­tion

Bon, tout le monde commence à avoir l’ha­bi­tude : un bon petit apt-get et roule ma poule !

# apt-get install ghc6 xmonad dwm-tools trayer rxvt-unicode nitro­gen

Je crois qu’il y a là tout ce qu’il faut. Si ça ne marche pas comme il faut, essayer d’ins­tal­ler les paquets libghc6…, c’est peut-être ça qu’il manque.

La confi­gu­ra­tion du bureau

Là c’est plutôt funky parce que le fichier de confi­gu­ra­tion est en Haskell ! Je vous avouer, je n’y comprends pas grand chose mais on devine ce qu’il faut faire en allant voir les exemples de confi­gu­ra­tion sur http://haskell.org/haskell­wiki/Xmonad/Config_archive.

Sinon, vous pouvez toujours prendre le mien et le mettre dans ~/.xmonad/xmonad.hs :

—Imports {{{
—A lot of imports. Actually, I don’t know which of them are truely useful
import XMonad
—Layouts
import XMonad.Layout
import XMonad.Layout.NoBor­ders
import XMonad.Layout.PerWorks­pace
import XMonad.Layout.LayoutHints
import XMonad.Layout.ThreeCo­lumns
import XMonad.Layout.ToggleLayouts
—Hooks
import XMonad.Hooks.Mana­geDocks
import XMonad.Hooks.EwmhDesk­tops
import XMonad.Hooks.Dyna­micLog (PP(..), dyna­micLogWi­thPP, wrap, defaultPP)
import XMonad.Hooks.Urgen­cyHook
—Actions
import XMonad.Actions.CycleWS
import XMonad.Actions.CycleRe­centWS
import XMonad.Actions.CopyWin­dow
import XMonad.Actions.DwmP­ro­mote
import XMonad.Actions.GridSe­lect
import XMonad.Actions.MouseRe­size
import XMonad.Actions.OnScreen
import XMonad.Actions.SinkAll
import XMonad.Actions.WindowGo
import XMonad.Util.Run (spawnPipe)
import quali­fied XMonad.StackSet as W
import quali­fied Data.Map as M
import Data.Bits ((.|.))
import System.IO (hPutS­trLn)
—}}}

—Con­trol Center {{{
—Colour scheme {{{
myNor­malBGCo­lor = « #2e3436 »
myFo­cu­sedBGCo­lor = « #414141 »
myNor­malFGCo­lor = « #babdb6 »
myFontCo­lor = « #73d216 »
myFo­cu­sedFGCo­lor = « #73d216 »
myUr­gentFGCo­lor = « #f57900 »
myUr­gentBGCo­lor = myNor­malBGCo­lor
mySe­pe­ra­torCo­lor = « #2e3436 »
—}}}

—Icon packs can be found here:
—http://robm.selfip.net/wiki.sh/-main/DzenI­conPacks
myBit­mapsDir = « /home/luc/.share/icons/dzen »
myFont = « -misc-fixed-*-*-*-*-10-*-*-*-*-*-iso10646–1 »
—}}}

—Works­paces {{{
myWorks­paces :: [Works­pa­ceId]
myWorks­paces = map show [1..9 :: Int]
—}}}

—Define Modi­fier key
—mod1Mask : Alt, mod4Mask : Super
—myModMask = mod1Mask
myModMask = mod4Mask

—Define termi­nal
myTer­mi­nal = « urxvt -pe tabbed »
—Define border­width
myBor­derWidth = 1

—Key­bin­dings {{{
myKeys conf@(XCon­fig {modMask = modm}) = M.fromList $
[
((modm , xK_p), spawn (« exec `dmenu_path | dmenu -fn ' » ++ myFont ++ « ' -nb ' » ++ myNor­malBGCo­lor ++ « ' -nf ' » ++ myNor­malFGCo­lor ++ « ' -sb ' » ++ myFo­cu­sedBGCo­lor ++ « ' -sf ' » ++ myFontCo­lor ++ « '` »))
, ((modm , xK_i), spawn (« exec icewea­sel »))
, ((modm , xK_f), sendMes­sage ToggleS­truts >> sendMes­sage ToggleLayout)
, ((modm , xK_n), spawn (« life­rea »))
, ((modm , xK_m), spawn (« icedove »))
, ((0 , 0×1008ff59), spawn (« xran­dr—auto »))
, ((0 , 0×1008ff11), spawn (« amixer -q set Master 1– unmute »))
, ((0 , 0×1008ff13), spawn (« amixer -q set Master 1+ unmute »))
, ((0 , 0×1008ff12), spawn (« amixer -c 0 set Master toggle »))
]
++

—Remap swit­ching works­paces to M-[&é"'(-è_ç]
—I get these codes with xev. With this codes, I’m sure to get the key I want
[((m .|. modm, k), windows $ f i)
| (i, k) <- zip (XMonad.works­paces conf) [0×26,0xe9,0×22,0×27,0×28,0×2d,0xe8,0×5f,0xe7]
, (f, m) <- [(W.gree­dyView, 0), (W.shift, shiftMask)]]
—}}}

statusBarCmd= « dzen2 -p -h 16 -ta l -bg ' » ++ myNor­malBGCo­lor ++ « ' -fg ' » ++ myNor­malFGCo­lor ++ « ' -w 300 -sa c -fn ' » ++ myFont ++ « ' »

—Main {{{
main = do
statusBarPipe <- spawnPipe statusBarCmd
xmonad $ withUr­gen­cyHook dzenUr­gen­cyHook { args = ["-bg", « dark­green », « -xs », « 1 »] } $ defaultCon­fig {
modMask = myModMask
, borderWidth = myBor­derWidth
, termi­nal = myTer­mi­nal
, normalBor­derCo­lor = myNor­malBGCo­lor
, focu­sedBor­derCo­lor = myFo­cu­sedFGCo­lor
, mana­geHook = mana­geHook defaultCon­fig <+> myMa­na­geHook
, layoutHook = smartBor­ders $ avoidS­truts $ toggleLayouts Full $ layoutHook defaultCon­fig
, works­paces = myWorks­paces
, logHook = dyna­micLogWi­thPP $ myPP statusBarPipe
, keys = \c -> myKeys c `M.union` keys defaultCon­fig c
}
—}}}

—Win­dow rules (floa­ting, tagging, etc) {{{
myMa­na­geHook = compo­seAll . concat $
[—The appli­ca­tions that go to Works­pace 1
—[ className =? b—> doF (W.shift « 1 ») | b <- myClassWebS­hifts]
—The appli­ca­tions that float
[ className =? i—> doFloat | i <- myClassF­loats]
—The appli­ca­tions to ignore
, [ className =? j—> doIgnore | j <- myClassI­gnore]
, [ title =? k—> doIgnore | k <- myTit­leI­gnore]
]
where
—The appli­ca­tions that go to Works­pace 1
—myC­lassWebS­hifts = ["Icewea­sel", « Life­rea »]
myClassF­loats = ["Mplayer", « Gimp »]
myClassI­gnore = ["stalo­ne­tray", « trayer »]
myTit­leI­gnore = ["xfce4-noti­fyd"]
—}}}

—Dzen Pretty Prin­ter {{{
—Sto­len from Rob [1] and modi­fied
—[1] http://haskell.org/haskell­wiki/Xmonad/Config_archive/Robert_Manea%27s_xmonad.hs
myPP handle = defaultPP {
ppCurrent = wrap (« ^fg( » ++ myFontCo­lor ++ « )^bg( » ++ myFo­cu­sedBGCo­lor ++ « )^p(4) ») « ^p(4)^fg()^bg() »,
ppUrgent = wrap (« ^fg( » ++ myUr­gentFGCo­lor ++ « )^bg( » ++ myUr­gentBGCo­lor ++ « )^p(4) ») « ^p(4)^fg()^bg() »,
ppVi­sible = wrap (« ^fg( » ++ myNor­malFGCo­lor ++ « )^bg( » ++ myNor­malBGCo­lor ++ « )^p(4) ») « ^p(4)^fg()^bg() »,
ppSep = « ^fg( » ++ mySe­pe­ra­torCo­lor ++ « )^r(3×3)^fg() »,
ppLayout = (\x -> case x of
« Tall » -> «  ^i( » ++ myBit­mapsDir ++ « /tall.xbm)  »
 »Mirror Tall" -> «  ^i( » ++ myBit­mapsDir ++ « /mtall.xbm)  »
« Full » -> «  ^i( » ++ myBit­mapsDir ++ « /full.xbm)  »
 »ThreeCol" -> «  ^i( » ++ myBit­mapsDir ++ « /three­col.xbm)  »
« Hinted Tall » -> «  ^i( » ++ myBit­mapsDir ++ « /tall.xbm)  »
 »Hinted Mirror Tall" -> «  ^i( » ++ myBit­mapsDir ++ « /mtall.xbm)  »
« Hinted Full » -> «  ^i( » ++ myBit­mapsDir ++ « /full.xbm)  »
 »Hinted ThreeCol" -> «  ^i( » ++ myBit­mapsDir ++ « /three­col.xbm)  »
_ -> «   » ++ x ++ «   »
),
ppTitle = wrap ( »^fg(«  ++ myFontCo­lor ++  »)") « ^fg() » ,
ppOut­put = hPutS­trLn handle
}
—}}}

Je pense que vous arri­ve­rez à comprendre quelle partie de code fait quoi (sauf peut-être la partie des layouts, mais je n’ai pas cher­ché à comprendre moi non plus). Pour connaître les combi­nai­sons de touches par défaut, je ne peux que vous encou­ra­ger à consul­ter la cheat sheet Xmonad.

Bon, main­te­nant qu’on a un beau fichier de conf, on peut le compi­ler avec

$ xmonad—­re­com­pile

Ok, ça c’est fait, Xmonad est prêt… mais pas le reste

Confi­gu­ra­tion de la barre d’in­for­ma­tions

Pour avoir une belle barre pleine d’in­for­ma­tions (et surtout avec les bureaux et le titre de la fenêtre en cours), il va falloir mettre un peu les mains dans le cambouis (encore !).

Sur mon screen­shot, vous pouvez voir un ensemble d’in­for­ma­tion comme l’état de la batte­rie, le volume, etc. Tout ceci est généré par un script shell dont le résul­tat est envoyé dans dzen. Et juste à côté, il y a une zone de noti­fi­ca­tion (le tray) qui me permet de toujours avoir mes appli­ca­tions préfé­rées sous la main.

En gros, vous pouvez affi­cher ce que vous voulez, du moment que vous pouvez l’ob­te­nir par des commandes du termi­nal.

Pour le mien (oui, il est en zsh, c’est comme ça qu’é­tait le script de base que j’ai récu­péré et je me suis pas amusé à essayer de voir si ça marchait toujours en bash), c’est :

#!/bin/zsh

# main inter­val in seconds
INTERVAL=0.5

# path to the icons
DZEN_ICONPATH='/home/luc/.share/icons/dzen’

# colors defi­ni­tion
RED='#FF0000'
ORANGE='#FF9900'
GREEN='#00FF00'
WHITE='#FFFFFF’

#SEPARATOR='^p(3)^r(3×3)^p(3)'
SEPARATOR=' – '
###################
## Date and Hour ##
###################

HOUR_FORMAT='%H:%M:%S’
DATE_FORMAT='%A %d %B %Y’

fdate() {
date +$DATE_FORMAT
}

fhour() {
date +$HOUR_FORMAT
}

##############
## Cpu temp ##
##############

fcpu­temp() {
# print -n `sen­sors | grep -A 1 'Core0 Temp’ | cut -c15–16 | sed '/^$/d’`
TEMP=`sen­sors | grep -A 1 'temp1' | cut -c15–16 | sed '/^$/d’`
if [[ $TEMP > 69 ]]
then
ICOTEMP="${DZEN_ICONPATH}/temp_high.xbm"
TEMPCOLOR=$RED
else
if [[ $TEMP > 54 ]]
then
ICOTEMP="${DZEN_ICONPATH}/temp_med.xbm"
TEMPCOLOR=$ORANGE
else
ICOTEMP="${DZEN_ICONPATH}/temp_low.xbm"
TEMPCOLOR=$GREEN
fi
fi
print -n `echo « ^fg(${TEMPCOLOR})^i($ICOTEMP)^fg() $TEMP C »`
}

##################
## Volume gauge ##
##################

fvolume() {
if [[ `amixer -c0 get Master | awk '/^ Mono/ { print $6 }'| sed -e « s/\[// » | sed -e « s/\]// »` == « on » ]]
then
VOLUME=`amixer -c0 get Master | awk '/^ Mono/ { print $4 }' | sed -e « s/\[// » | sed -e « s/\]// » | sed -e « s/%// »`
else
VOLUME=0
fi
if [[ $VOLUME > 66 || $VOLUME == 100 ]]
then
ICOVOLUME="${DZEN_ICONPATH}/vol-hi.xbm"
else
if [[ $VOLUME > 33 ]]
then
ICOVOLUME="${DZEN_ICONPATH}/vol-med.xbm"
else
if [[ $VOLUME > 0 ]]
then
ICOVOLUME="${DZEN_ICONPATH}/vol-low.xbm"
else
ICOVOLUME="${DZEN_ICONPATH}/vol-mute.xbm"
fi
fi
fi
print -n `echo « ^i($ICOVOLUME) ${VOLUME}% »`
}

###############
## Band­width ##
###############

# network inter­face
finter­face() {
if [[ `sudo ethtool eth0 | awk '/Link detec­ted/ { print $3 }'` == « no » ]]
then
if [[ `sudo iwcon­fig wlan0 | awk '/ESSID:off/ { print $4 }'` == « ESSID:off/any » ]]
then
LINK=0
INTERFACE=""
else
INTERFACE="wlan0"
LINK=1
fi
else
INTERFACE="eth0"
LINK=1
fi
}

fbwup() {
if [[ $LINK == 1 ]]
then
BW_UP=`ifstat -i $INTERFACE 0.1 1 | awk '/[0–9]*\.[0–9]*/ { print $2 }'`
else
BW_UP=""
fi
}

fbwdown() {
if [[ $LINK == 1 ]]
then
BW_DOWN=`ifstat -i $INTERFACE 0.1 1 | awk '/[0–9]*\.[0–9]*/ { print $1 }'`
else
BW_DOWN=""
fi
}

#############
## Battery ##
#############
fac() {
if [[ `acpi -a | awk '/Adap­ter/ { print $3 }'` == « on-line » ]]
then
ICOAC="${DZEN_ICONPATH}/ac.xbm"
else
ICOAC=""
fi
print -n `echo « ^i($ICOAC) »`
}

fbat() {
BAT=`acpi -b | awk '/Battery/ { print $4 }' | sed -e « s/[%,]//g »`
BAT=$(print -n $BAT)
if [[ $BAT > –1 ]]
then
if [[ $BAT > 70 || $BAT == 100 ]]
then
ICOBAT="${DZEN_ICONPATH}/battery_full.xbm"
BATCOLOR=$GREEN
BAT="${BAT}%"
else
if [[ $BAT > 45 ]]
then
ICOBAT="${DZEN_ICONPATH}/battery_good.xbm"
BATCOLOR=$ORANGE
BAT="${BAT}%"
else
if [[ $BAT > 10 ]]
then
ICOBAT="${DZEN_ICONPATH}/battery_low.xbm"
BATCOLOR=$RED
BAT="${BAT}%"
else
ICOBAT="${DZEN_ICONPATH}/battery_empty.xbm"
BATCOLOR=$RED
BAT="${BAT}%"
fi
fi
fi
print -n `echo «  ^fg(${BATCOLOR})^i($ICOBAT)^fg() ${BAT} »`
else
print -n ""
fi
}

##########
## Main ##
##########

while true; do
HOUR=$(fhour)
DATE=$(fdate)
CPUTEMP=$(fcpu­temp)
VOLUME=$(fvolume)
finter­face
fbwup $INTERFACE
fbwdown $INTERFACE
AC=$(fac)
PBAT=$(fbat)

print « ${AC} ${PBAT} ${SEPARATOR} ${VOLUME} ${SEPARATOR} ${INTERFACE}: ^fg(${GREEN})^p(3)^i(${DZEN_ICONPATH}/arr_down.xbm)^fg(white)${BW_DOWN} kB/s^fg(${RED})^i(${DZEN_ICONPATH}/arr_up.xbm)^fg(white)${BW_UP} kB/s^fg() ${SEPARATOR} ${CPUTEMP} ${SEPARATOR} ${HOUR} ${SEPARATOR} ${DATE} »

sleep $INTERVAL
done

C’est bien beau, mais comment je fais pour mettre tout ça dans la barre ?

Patience, jeune Pada­wan !

Main­te­nant, il faut piper ce résul­tat par le programme dzen. Moi je le fais dans le .xini­trc, d’autres dans le script lui-même. C’est une ques­tion de goût.

Mon .xini­trc :

#!/bin/sh
#
# ~/.xini­trc
#
# Execu­ted by startx (run your window mana­ger from here)
#

# Start URxvt daemon so we can quickly open the other clients
urxvtd -q -o -f
[[ -x « /usr/bin/unclut­ter » -a -z « `pidof unclut­ter` » ]] && \
unclut­ter -idle 5 -root&

# Set Wall­pa­per with nitro­gen
eval `nitro­gen—­res­to­re` &

# Set mouse cursor and back­ground colour
xsetroot -cursor_name left_ptr -solid '#090909' &

# Launch tray and status­bar
~/.dzen.sh | dzen2 -e 'onstart=lower’ -p -ta r -fn '-misc-fixed-*-*-*-*-8-*-*-*-*-*-*-*' -bg '#2e3436' -fg '#babdb6' -h 16 -x 300 -y 0 -w 624 &
trayer—edge top—a­lign right—­wid­th­type pixel—­width 100—SetPar­tialS­trut true—SetDo­ckType true—­height­type pixel—­height 16—tint 0×2e3436—­trans­pa­rent true—al­pha 0 &
# Launch appli­ca­tions on boot :
xfce4-clip­man &
icedove &
life­rea &
irexec &
xfce4-noti­fyd &
wicd-client &
# Launch WM
exec xmonad

Vous pouvez voir dans ce script le lance­ment d’un certain nombre d’ap­pli­ca­tions :

  • le démon urxvt (le termi­nal),
  • unclut­ter qui permet de cacher la souris au bout de 5 secondes,
  • nitro­gen pour avoir un fond d’écran,
  • ~/.dzen.sh pour la barre d’in­fos,
  • trayer pour la barre de noti­fi­ca­tion,
  • xfce4-clip­man pour l’his­to­rique des copier,
  • icedove pour mes mails,
  • life­rea pour mes flux rss,
  • irexec pour ma télé­com­mande (voir ce post)
  • xfce4-noti­fyd pour rece­voir les noti­fi­ca­tions de mes applis (genre : nouveau mail, flux mis à jour, etc.)
  • wicd-client pour le network-mana­ger

Confi­gu­ra­tion d’Urxvt

Plus qu’à confi­gu­rer Urxvt et c’est fini ! Mettez ceci dans le .Xdefaults :

URxvt*­trans­pa­rent:true
URxvt*­tintCo­lor:white
URxvt*­sha­ding:30
URxvt*­sa­veLines:12000
URxvt*­fo­re­ground:White
URxvt*­back­ground:black
URxvt*­font:xft:DejaVu Sans Mono:pixel­size=11
URxvt*s­crollBar:false
URxvt*s­crollBar_right:false
URxvt*s­croll­style:rxvt
URxvt*­fa­ding:30
URxvt*i­so14755_52:false
URxvt*­perl-ext-common:default,matcher
URxvt*urlLaun­cher:icewea­sel
URxvt.colorUL:#86a2be
URxvt.keysym.Control-Up:\033[1;5A
URxvt.keysym.Control-Down:\033[1;5B
URxvt.keysym.Control-Left:\033[1;5D
URxvt.keysym.Control-Right:\033[1;5C
URxvt.keysym.M-Down:\033[1;3B
URxvt.keysym.M-Up:\033[1;3A
URxvt.keysym.M-Left:\033[1;3D
URxvt.keysym.M-Right:\033[1;3C

Beau­coup de trucs ésoté­riques dans ce fichier mais rete­nez bien les 8 derniers : il permettent d’avan­cer/recu­ler d’un mot dans vim et dans le termi­nal avec Ctrl+­flèche. Sans ça, point de salut ! Et pour avoir utilisé mon pc pendant quelques mois sans cette fonc­tion­na­lité, je peux vous dire qu’elle est vrai­ment utile !

Conclu­sion

Ouf ! Si ce n’est pas le plus long post que j’ai jamais fait, il est au moins dans le top 3 ! Ça doit bien faire un mois que je le prépare et puis, vous savez ce que c’est, on rentre du boulot, on est crevé, on a envie de rien faire…

J’es­père que cet article aura donné envie à quelques un de tester Xmonad et de faire mumuse avec. Je suis conscient que ma confi­gu­ra­tion et mes scripts sont perfec­tibles mais là, sur mon netbook, ils sont ample­ment suffi­sants.

Ah ! J’ou­bliais ! Il y a un problème rela­ti­ve­ment génant dans ma confi­gu­ra­tion (ou alors c’est un bug, je n’en suis pas sûr) : pas moyen d’af­fi­cher une vidéo flash en plein écrant à partir d’in­ter­net ! (Mplayer le fait très bien.) Sachant que de toute façon, flash çapu­say­pa­libre, ça ne me dérange pas trop, mais je ne voudrais pas que des testeurs de ma conf me reproche ce compor­te­ment. D’ailleurs, si quelqu’un a une idée (moi j’ai rien trouvé), qu’il me le fasse savoir.

Amusez-vous bien.

Mes fichiers :

EDIT : Appa­rem­ment, la conf que j’avais mis ne compi­lait pas. J’ai rema­nié et j’ai remis la bonne version. Et aussi, pensez à vous baser sur les fichiers plutôt que sur ce qui est écrit : la remise en page est plutôt fasti­dieuse pour ce genre de chose dans Dotclear dont peut amener un certain nombre d’er­reurs.

RE-EDIT : Je viens de passer à Word­press pour mon moteur de blog et je n’ai pas trouvé comment lui faire accep­ter des fichiers sans exten­sions, donc le .xini­trc et le .Xdefaults ont une exten­sion en .sh si vous les télé­char­gez. Pensez à les renom­mer.