Tuesday, August 05, 2008

Xmonad: optimizing working space

Some later I wrote about Gnome and how to change default metacity wm (that very good and cannot be in use if you want to make your works faster) with windowmaker.
I've decide reject Gnome and use pure wm without desktop environment, but also I've decided to try tiling wm written on Haskell - XMonad.
It's really usable, for indication I'm using a dzen2 and some set of handmade icons.
Here my ~/.xmonad/xmonad.hs

import XMonad hiding ((|||))
import XMonad.Operations
import System.Exit
import Graphics.X11
import System.IO

import qualified XMonad.StackSet as W
import qualified Data.Map as M

import XMonad.Hooks.DynamicLog
import XMonad.Hooks.UrgencyHook
import XMonad.Layout.Tabbed
import XMonad.Layout.NoBorders
import XMonad.Hooks.ManageDocks
import XMonad.Layout.Grid
import XMonad.Layout.ToggleLayouts


import XMonad.Layout.LayoutCombinators ((|||))
import XMonad.Util.Run (spawnPipe)


------------------------------------------------------------------------
-- Common variables
--
myTerminal = "urxvt"
myBorderWidth = 1
myModMask = mod4Mask
myNumlockMask = mod2Mask
myWorkspaces = ["Shells","Emacs","WWW","IM", "Docs", "Media" ] ++ map show [6..9]
myNormalBorderColor = "#3e7464"
myFocusedBorderColor = "#0b610b"
myNormalBg = "#0b0b0b"
myNormalFg = "#385c64"
myFocusedBg = "#598867"
myFocusedFg = "#760202"
myFont = "-misc-*-*-*-*-*-10-*-*-*-*-*-*-*"
myBitmapsDir = "/home/tirra/Icons/"

myDefaultGaps :: [(Int,Int,Int,Int)]
myDefaultGaps = [(14,0,0,0)] -- 15 for default dzen

menuCmd = "dmenu -fn '" ++ myFont ++ "' -nb '" ++ myNormalBg ++ "' -nf '" ++ myNormalFg ++ "' -sb '" ++ myFocusedBg ++ "' -sf '" ++ myFocusedFg ++ "'"
dzen2 = "dzen2 -p -h 14 -ta l -bg '" ++ myNormalBg ++ "' -fg '" ++ myNormalFg ++ "' -w 600 -sa c -fn '" ++ myFont ++ "'"

------------------------------------------------------------------------
-- Key bindings. Add, modify or remove key bindings here.
--
myKeys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
[ ((modMask, xK_r ), spawn ("exec `dmenu_path | " ++ menuCmd ++ "`"))
, ((modMask, xK_q ), kill)
, ((modMask .|. shiftMask, xK_a ), spawn "urxvt")
, ((modMask .|. shiftMask, xK_e ), spawn "emacs")
, ((modMask .|. shiftMask, xK_m ), spawn "urxvt -e mutt")
, ((modMask, xK_space ), sendMessage NextLayout)
, ((modMask .|. shiftMask, xK_space ), setLayout $ XMonad.layoutHook conf)
, ((modMask, xK_n ), refresh)
, ((modMask, xK_Tab ), windows W.focusDown)
, ((modMask, xK_j ), windows W.focusDown)
, ((modMask, xK_k ), windows W.focusUp )
, ((modMask, xK_m ), windows W.focusMaster )
, ((modMask, xK_Return), windows W.swapMaster)
, ((modMask .|. shiftMask, xK_j ), windows W.swapDown )
, ((modMask .|. shiftMask, xK_k ), windows W.swapUp )
, ((modMask, xK_h ), sendMessage Shrink)
, ((modMask, xK_l ), sendMessage Expand)
, ((modMask, xK_t ), withFocused $ windows . W.sink)
, ((modMask , xK_comma ), sendMessage (IncMasterN 1))
, ((modMask , xK_period), sendMessage (IncMasterN (-1)))
, ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess))
, ((modMask , xK_f), (sendMessage (Toggle "Full")) >> sendMessage (Toggle "Grid"))
, ((modMask .|. shiftMask, xK_r ),
broadcastMessage ReleaseResources >> restart "xmonad" True)
]

++

--
-- mod-[1..9], Switch to workspace N
-- mod-shift-[1..9], Move client to workspace N
--
[((m .|. modMask, k), windows $ f i)
| (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9]
, (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]]


------------------------------------------------------------------------
-- Mouse bindings: default actions bound to mouse events
--
myMouseBindings (XConfig {XMonad.modMask = modMask}) = M.fromList $

-- mod-button1, Set the window to floating mode and move by dragging
[ ((modMask, button1), (\w -> focus w >> mouseMoveWindow w))

-- mod-button2, Raise the window to the top of the stack
, ((modMask, button2), (\w -> focus w >> windows W.swapMaster))

-- mod-button3, Set the window to floating mode and resize by dragging
, ((modMask, button3), (\w -> focus w >> mouseResizeWindow w))

-- you may also bind events to the mouse scroll wheel (button4 and button5)
]

------------------------------------------------------------------------
-- Layouts:

-- You can specify and transform your layouts by modifying these values.
-- If you change layout bindings be sure to use 'mod-shift-space' after
-- restarting (with 'mod-q') to reset your layout state to the new
-- defaults, as xmonad preserves your old layout settings by default.
--
-- The available layouts. Note that each layout is separated by |||,
-- which denotes layout choice.
--


myTabConfig = defaultTheme {
activeColor = "#6666cc"
, activeBorderColor = "#000000"
, inactiveColor = "#666666"
, inactiveBorderColor = "#000000"
, decoHeight = 10
}


------------------------------------------------------------------------
-- Window rules:

myManageHook = composeAll [ className =? "Firefox-bin" --> doF(W.shift "WWW")
, className =? "Iceweasel" --> doF(W.shift "WWW")
, className =? "Gajim.py" --> doF(W.shift "IM")
, className =? "Evince" --> doF(W.shift "Docs")
, className =? "Emacs" --> doF(W.shift "Emacs")
, title =? "mutt" --> doF(W.shift "WWW")
, className =? "URxvt" --> doF(W.shift "Shell")
, className =? "Stardict" --> doF(W.shift "Docs")
]



------------------------------------------------------------------------
-- dzen2 configuration
--

myPP h = defaultPP {
ppOutput = hPutStrLn h
, ppCurrent = dzenColor myFocusedFg myFocusedBg . pad
, ppVisible = dzenColor myNormalFg myNormalBg . pad
, ppTitle = dzenColor "#117418" "" . wrap "{ " " }"
, ppHiddenNoWindows = const ""
, ppWsSep = " ^fg(#113d41)^r(1x12)^fg() "
, ppSep = " ^fg(#113d41)^r(2x2)^fg() " -- "|"
-- , ppLayout = dzenColor "#946144" "" .
-- (\x -> case x of
-- "Tall" -> "^i(/home/dk/.icons/dzen2/tall.xbm)"
-- "Mirror Tall" -> "^i(/home/dk/.icons/dzen2/mtall.xbm)"
-- "Full" -> "^i(/home/dk/.icons/dzen2/full.xbm)"
-- )
}

myUrgencyHook = withUrgencyHook dzenUrgencyHook
{ args = ["-bg", myNormalBg, "-fg", "#117418"] }


------------------------------------------------------------------------
-- Run xmonad with the settings you specify. No need to modify this.
--
main = do din <- spawnPipe dzen2
xmonad $ myUrgencyHook $
defaultConfig {
-- simple stuff
terminal = myTerminal,
-- focusFollowsMouse = myFocusFollowsMouse,
borderWidth = myBorderWidth,
modMask = myModMask,
numlockMask = myNumlockMask,
workspaces = myWorkspaces,
normalBorderColor = myNormalBorderColor,
focusedBorderColor = myFocusedBorderColor,
defaultGaps = myDefaultGaps,

-- key bindings
keys = myKeys,
mouseBindings = myMouseBindings,

-- hooks, layouts
layoutHook = toggleLayouts (noBorders Full) $
smartBorders $ tiled ||| Mirror tiled ||| Full ||| Grid ||| tabbed shrinkText myTabConfig,
manageHook = (manageHook defaultConfig <+> myManageHook) <+> manageDocks,
logHook = dynamicLogWithPP $ myPP din
}
where
tiled = Tall 1 0.03 0.68

There are many tuning for yourself can be made. For dzen2 I wrote two files - one of this just runs it, other is a daemon that takes information and provide it to dzen2 panel.
Main file - ~/.xmonad/panel.sh

#!/bin/bash

export LANG=en_US.UTF-8
FONT='-misc-*-*-*-*-*-10-*-*-*-*-*-*-*'
DZEN="dzen2 -p -h 14 -fn $FONT"
BG="#0b0b0b"
FG="#385c64"
MONITOR=/home/tirra/.xmonad/monitor.pl

SW=1280
Y=0
X=600
$MONITOR | $DZEN -e '' -x $X -y $Y -ta r -bg $BG -w $(($SW-$X)) -fg $FG -sa c

And ~/.xmonad/monitor.pl where you need to made some modifications:

#!/usr/bin/perl

use strict;

###
# Configurable constants
#
## Xbm icons directory
use constant XBM_IDIR => '/home/tirra/Icons';
## Colors, separators and other stuff of this kind
use constant FG_COLOR => '#385c64';
use constant BG_COLOR => '#010101';
use constant TITLE_COLOR => '#946144';
use constant ELEM_COLOR => '#5c0000';
use constant ICON_COLOR => '#00baff';
use constant VSEP => '^fg(#113d41)^r(1x12)^fg()';
use constant ISEP => '^p(6)^fg(#113d41)^r(2x2)^fg()^p(6)';
## IDLE time
use constant IDLE => 1;
## Constants for CPU load measurment
use constant FSTAT => '/proc/stat';
use constant NCPUS => 2; # number of CPUs in system
## Battery constants
use constant BAT_STATUS => '/sys/class/power_supply/BAT1/status';
use constant BAT_CHARGE_NOW => '/sys/class/power_supply/BAT1/charge_now';
use constant BAT_CHARGE_FULL => '/sys/class/power_supply/BAT1/charge_full';
use constant BAT_FULL_ICON => XBM_IDIR . '/b_full.xbm';
use constant BAT_CRITICAL_ICON => XBM_IDIR . '/b_critical.xbm';
use constant BAT_EMPTY_ICON => XBM_IDIR . '/b_empty.xbm';
use constant BAT_25_ICON => XBM_IDIR . '/b_25.xbm';
use constant BAT_50_ICON => XBM_IDIR . '/b_50.xbm';
use constant BAT_75_ICON => XBM_IDIR . '/b_75.xbm';
use constant BAT_CHRG_ICON => XBM_IDIR . '/ac_01.xbm';
## Constants for memory monitoring
use constant MEMINFO => '/proc/meminfo';
## Net interface constants
use constant ROUTE => '/sbin/route';
use constant NETPATH => '/sys/class/net/';
use constant NET_UP_ICON => XBM_IDIR . '/arr_up.xbm';
use constant NET_DOWN_ICON => XBM_IDIR . '/arr_down.xbm';
use constant NET_PPPDOWN_ICON => XBM_IDIR . '/ppp_down.xbm';
use constant NET_PPPUP_ICON => XBM_IDIR . '/ppp_up.xbm';
use constant NET_ETHDOWN_ICON => XBM_IDIR . '/eth_down.xbm';
use constant NET_ETHUP_ICON => XBM_IDIR . '/eth_up.xbm';
use constant NET_WLANDOWN_ICON => XBM_IDIR . '/wlan_down.xbm';
use constant NET_WLANUP_ICON => XBM_IDIR . '/wlan_up.xbm';
## Jabber constatns
use constant JNAME => 'IM';
use constant ACOLOR => '#117418';
## Weather constants
use constant WTITLE => 'Out';
use constant WID => 'yourweatheridhere';
use constant WUPD_INT => 30 * 60;

######
# Cpu load measurment in percents.
#
my @cpus = undef;
my $stat_fh = undef;

sub init_cpus()
{
for (my $i = 0; $i < NCPUS; $i++) {
$cpus[$i] = { 'old_idle' => 1, 'old_load' => 1 };
}

open($stat_fh, '<', FSTAT) or die "Can't open file " . FSTAT . " for reading $!";
}

sub get_cpus_load_perc()
{
my @cpus_load = undef;

for (my $i = 0; $i < NCPUS; $i++) {
while (<$stat_fh>) {
next if (!/^cpu$i.+/);
# /proc/stat should contain all data about CPU in the following
# format(all data about particular CPU is written in one line):
# cpu:
#
my @load = split /\s/;
my $idle_time = $load[4];
my $load_time = $load[1] + $load[2] + $load[3] + $load[5] + $load[6] + $load[7];
$cpus_load[$i] = int(($load_time - $cpus[$i]->{'old_load'}) /
($idle_time + $load_time - $cpus[$i]->{'old_idle'} -
$cpus[$i]->{'old_load'}) * 100);
$cpus[$i]->{'old_idle'} = $idle_time;
$cpus[$i]->{'old_load'} = $load_time;
last if ($i++ == NCPUS);
}
}

die "Can't make seek on " . FSTAT . "$!" if (seek($stat_fh, 0, 0) < 0);
return @cpus_load;
}

sub show_cpu_info()
{
my @cpu_info = get_cpus_load_perc();
my $tmp = "";

print "^fg(" . TITLE_COLOR . ")CPU:^fg()^p(6)";
for (my $i = 0; $i < NCPUS; $i++) {
$tmp .= "$cpu_info[$i]%";
$tmp .= " " . VSEP . " " if ($i + 1 != NCPUS);
}

print $tmp;
}

######
# Laptop's battery state
#

# returns an array of two elements
# where first element is a current power of battery in percens
# and the second one is a boolean value showing if the battry is
# charged or not.
sub get_battery_state()
{
my @bat_state = undef;
my $bat_max = 0;
my $bat_cur = 0;

open(BFC, '<', BAT_CHARGE_FULL) or die "Can't open file " . BAT_CHARGE_FULL . "for reading $!";
while () {
$bat_max = $_;
last;
}

close(BFC);

open(BFC, '<', BAT_CHARGE_NOW) or die "Can't open file " . BAT_CHARGE_NOW . "for reading $!";
while () {
$bat_cur = $_;
last;
}

close(BFC);
$bat_state[0] = int($bat_cur / $bat_max * 100);

open(BFC, '<', BAT_STATUS) or die "Can't open file " . BAT_STATUS . "for reading $!";
while () {
$bat_state[1] = $_;
last;
}

close(BFC);


return @bat_state;
}

sub show_battery_info()
{
my @binfo = get_battery_state();
my $icon = BAT_EMPTY_ICON;

if($binfo[0] <= 1) {
$icon = BAT_EMPTY_ICON;
}
elsif($binfo[0] <= 10) {
$icon = BAT_CRITICAL_ICON;
}
elsif($binfo[0] <= 25) {
$icon = BAT_25_ICON;
}
elsif($binfo[0] <=50) {
$icon = BAT_50_ICON;
}
elsif($binfo[0] < 99) {
$icon = BAT_75_ICON;
}
elsif($binfo[0] >= 99) {
$icon = BAT_FULL_ICON;
}


print "^fg(" . ICON_COLOR . ")^i($icon)^fg()";
}

######
# Memory usage monitor.
#

sub get_meminfo()
{
my ($mtotal, $mactive, $stotal, $sfree, $cached) = (0, 0, 0, 0, 0);
my %minfo = undef;

open(MIF, '<', MEMINFO) or die "Can't open file " . MEMINFO . " for reading $!";
while () {
if (/^MemTotal:\s+(\d+).*$/) {
$mtotal = $1;
}
elsif (/^Active:\s+(\d+).*$/) {
$mactive = $1;
}
elsif (/^SwapTotal:\s+(\d+).*$/) {
$stotal = $1;
}
elsif (/^SwapFree:\s+(\d+).*$/) {
$sfree = $1;
}
elsif (/^Buffers:\s+(\d+).*$/) {
$cached = $1;
}
}

close(MIF);
$minfo{'in_use'} = int($mactive / $mtotal * 100);
$minfo{'cached'} = int($cached / $mtotal * 100);
$minfo{'swap'} = int(($stotal - $sfree) / $stotal * 100);

return %minfo;
}

sub show_mem_info()
{
my %minfo = get_meminfo();

print "^fg(" . ICON_COLOR . ")MEM:^fg() ";
print $minfo{'in_use'} . "% ";
}

######
# Network monitor.
#

my @old_netstat = (0, 0);
my $network_avail = 0;

# Get current speed of the interface and
# quantoty of received and transmitted trough it bytes
sub get_net_info(@_)
{
my $ifname = shift;
my $file = NETPATH . "/$ifname/statistics/rx_bytes";
my @stat = undef;

open(RXF, '<', $file) or die "Can't open file $file for reading $!";
my $rx_bytes = ;
close(RXF);

$file = NETPATH . "/$ifname/statistics/tx_bytes";
open(TXF, '<', $file) or die "Can't open file $file for reading $!";
my $tx_bytes = ;
close(TXF);

$stat[0] = int(($rx_bytes - $old_netstat[0]) / 1024);
$stat[1] = int(($tx_bytes - $old_netstat[1]) / 1024);
$old_netstat[0] = $rx_bytes;
$old_netstat[1] = $tx_bytes;

return @stat;
}

sub show_net_info()
{
my $cmd = ROUTE . " -n | grep -e '^0.0.0.0' | awk '{print \$8}'";
my $iface = `$cmd`;
my $iconppp = NET_PPPDOWN_ICON;
my $iconwlan = NET_WLANDOWN_ICON;
my $iconeth = NET_ETHDOWN_ICON;

chomp($iface);

if ($iface eq 'ppp0') {
$iconppp = NET_PPPUP_ICON;
}

if ($iface eq 'wlan00') {
$iconwlan = NET_WLANUP_ICON;
}

if ($iface eq 'eth0') {
$iconeth = NET_ETHUP_ICON;
}


if ($iface eq '') {
$network_avail = 0;
@old_netstat = (0, 0);
print " ^fg(" . ICON_COLOR . ")^i($iconeth)^fg()";
print " ^fg(" . ICON_COLOR . ")^i($iconwlan)^fg()";
print " ^fg(" . ICON_COLOR . ")^i($iconppp)^fg()";
return;
}

my @stat = get_net_info($iface);

$network_avail = 1;
print " ^fg(" . ICON_COLOR . ")^i($iconeth)^fg()";
print " ^fg(" . ICON_COLOR . ")^i($iconwlan)^fg()";
print " ^fg(" . ICON_COLOR . ")^i($iconppp)^fg()";

# print "^fg(" . TITLE_COLOR . ")$iface:^fg()^p(6)$stat[0]";
# print "^fg(" . ELEM_COLOR . ")^i(" . NET_DOWN_ICON . ")^fg() ";
# print VSEP . " $stat[1]^fg(" . ELEM_COLOR . ")^i(" . NET_UP_ICON . ")^fg()";
}

####
# Jabber monitor.
#

sub get_nmessages()
{
my @procs = split('\n', `ps aux | grep gajim`);
my $found = 0;

foreach (@procs) {
next if (/.*?\s+e?grep.*$/);
$found = 1;
last;
}
if ($found) {
my $num = `gajim-remote get_unread_msgs_number`;
chomp($num);
return $num;
}

return '-';
}

sub show_jabberinfo()
{
my $nmsg = get_nmessages();
print "^fg(" . ICON_COLOR . ")" . JNAME . ":^fg()^p(6)";
if (($nmsg ne '-') && ($nmsg > 0)) {
print "^fg(" . ACOLOR . ")$nmsg^fg()";
}
else {
print "$nmsg";
}
}

###
# Weather monitor
#

my $last_wupd_time = 0;
my $last_temp = undef;

sub get_temperature()
{
my $cmd = 'weather -i ' . WID;
my @data = split(/\n/, `$cmd`);
my $temp = undef;

foreach (@data) {
next until(/\s+Temperature:.+?\((-?\d+)\s+C\).*$/);
$temp = "$1";
$temp = "+$temp" if ($1 > 0);
$temp .= "C";
last;
}

return $temp;
}

sub show_temperature()
{
my $time = time();
my $temp = undef;

if (!$network_avail) {
$temp = '-';
}
elsif (($time - $last_wupd_time) >= WUPD_INT) {
$temp = get_temperature();
$last_temp = $temp;
$last_wupd_time = $time;
}
else {
$temp = $last_temp;
}

print "^fg(" . ICON_COLOR . ")" . WTITLE . ":^fg()^p(6)$temp";
}

####
# Time
#

sub show_time()
{
my $time = `date +'%H:%M'`;

chomp($time);
print $time;
}

my $old_fh = select(STDOUT);
$| = 1;
select($old_fh);
init_cpus();
for (;;) {
print ISEP;
show_time();
print ISEP;
show_jabberinfo();
print ISEP;
show_temperature();
print ISEP;
show_mem_info();
print ISEP;
show_battery_info();
show_net_info();
print "\n";
sleep(IDLE);
}

There are my Icons : Icons.tar.gz

And this is a snap of my workspace:
Free Image Hosting at www.ImageShack.us

3 comments:

soko1 said...

ะช!

Dondy said...

thanks for posting that interresting config - got me to customize my own configuration more :)

Anonymous said...

Hi, Icons.tar.gz off!