monochromatic

monochromatic blog: http://blog.z3bra.org
git clone git://z3bra.org/monochromatic
Log | Files | Refs

meeting-at-the-bar.txt (20425B)


      1 # [Meeting at the bar](#)
      2 ## — 02 April, 2014
      3 
      4 ### Introduction
      5 
      6 Hi everybody ! This post is more or less the continuation of my previous one:
      7 [Home, sweet home](/2013/10/home-sweet-home.html). We will take desktop
      8 customisations a little further here, so I'll assume that you, the reader, know
      9 the base of linux system administration.
     10 
     11 Status bar can display anything you feed them with, so let's make that food
     12 useful to us. We will learn how to display system informations on your screen.
     13 
     14 Here we go!
     15 
     16 ### Summary
     17 
     18 * [head-up display](#hud)
     19 * [fetching informations](#grab)
     20 
     21 
     22 <h3 id='hud'>head-up display</h3>
     23 
     24 First of all, let's understand what an HUD is. Gamers can go to the next
     25 paragraph. An HUD display some information you (most of the time) wants to see.
     26 In video-games, that will be your life/armor points, or the number of ammos
     27 loaded in your gun. Those informations are almost always visible and are updated
     28 in real-time.
     29 
     30 But we're not playing video games here. And you're probably not reading with a
     31 loaded gun in your hand. So, sitting in front of your computer, you want other
     32 kind of informations. We will talk about those informations later.  
     33 First, let's see HOW we will display them on-screen. I currently know 4 ways of
     34 doing it (understand, I've not tried the alternatives):
     35 
     36 * dzen
     37 * conky
     38 * tmux
     39 * bar
     40 
     41 #### dzen
     42 
     43 From the official website:
     44 
     45 > Dzen is a general purpose messaging, notification and menuing program for
     46 > X11. It was designed to be scriptable in any language and integrate well
     47 > with window managers like dwm, wmii and xmonad though it will work with
     48 > any windowmanger.
     49 
     50 Seems great ! Dzen is really simple to use, pipe text to it, and watch it
     51 appear on your screen:
     52 
     53     echo shblah | dzen2 -w 120 -x 10 -y 10 -p 5
     54 
     55 I'm not a huge fan of dzen anymore. I used to be (You can do awesome things
     56 with dzen, just check [earsplit's dekstop](http://i.imgur.com/bZegioR.gif), but
     57 I discovered a new tool that is basically dzen simplified, and written on top of
     58 XCB (see the fourth alternative: bar).
     59 
     60 #### conky
     61 
     62 Here comes the king of HUDs, ladies and gentlemen, please put a knee to the
     63 ground!  
     64 Conky's job is to display every possible information on your screen, in a really
     65 eye-candy way. I made
     66 [this](http://pix.toile-libre.org/upload/original/1360670013.jpg), monthes ago
     67 using conky (do not ask for configs or wallpaper, I don't have them anymore).
     68 
     69 It is extensible in lua and has a heavy set of features built-in.
     70 Check this out: [conky's variables](http://conky.sourceforge.net/variables.html)
     71 I've been using conky for weeks, but I just got bored. I realised that I did not
     72 need so much infos in real-time. But that was a fun period !
     73 
     74 Conky reads its configuration from the `~/.conkyrc` file. A minimalist config
     75 would be:
     76 
     77     cat <<EOF > ~/.conkyrc
     78     alignment tl
     79     gap_x 10
     80     gap_y 40
     81 
     82     TEXT
     83     shblah
     84     EOF
     85     conky &
     86 
     87 But for such a simple thing, that's a bit overkill.  
     88 Note that there is also conky-cli that outputs informations to stdout. That is
     89 useful to build informations lines to feed a bar with. To have a quick idea of
     90 how this works, check this
     91 [nice forum post](http://nixers.net/showthread.php?tid=117) by jmbi.
     92 
     93 #### tmux statusbar
     94 
     95 This one is a bit out of competition, but worth mentionning. TMux stands for
     96 terminal multiplexer. Short story, you can have multiple terminal within a
     97 single terminal window. But it offers a nice feature: a status bar, that is
     98 displayed at the bottom of the window. You can run any command in here, or even
     99 shell scripts, and the output will sit just there.
    100 
    101 Tmux actually have a left and right status bar. So just pick one (or both) to
    102 display some infos:
    103 
    104     echo "set-option -g status-left "shblah" >> ~/.tmux.conf
    105     tmux
    106 
    107 Phyrne wrote a [nice article](http://calummacrae.blogspot.co.uk/2012/12/dropping-status-bars-for-tmux-as-im.html)
    108 about it. Just read it.
    109 
    110 #### bar
    111 
    112 My last and prefered option, the bar made by LemonBoy !  
    113 This small piece of software is a stripped down clone of dzen, written on top
    114 of XCB (a better librairy to communicate with the X server). It's fast, it's
    115 light and you can even script it, as it now has a clickable item that you can
    116 use to start applications. More infos [here](https://github.com/LemonBoy/bar).
    117 
    118 Bar is pretty easy to use. It works the same way dzen does, by piping text to
    119 it:
    120 
    121     echo 'shblah' | bar -g 120x20+10+80 -p
    122 
    123 Starting from now, I will use bar as my tool of choice, but use the one you
    124 prefer, they can all do such a thing (well, conky has pretty much everything
    125 done for you, but meh) !
    126 
    127 <h3 id='grab'>fetching informations</h3>
    128 
    129 Once you now which tool you'll use to display your informations, you need to
    130 decide which one you want. For the purpose of the article, I'll settle on 8 of
    131 them:
    132 
    133 * current date / time
    134 * battery level
    135 * sound level
    136 * CPU load
    137 * RAM used
    138 * network connection state
    139 * window manager groups
    140 * mpd's current playing song
    141 
    142 I choosed those to show you many ways to fetch informations on your computer.
    143 
    144 Before going any further, I need to introduce you to the tools we'll need, and
    145 that we just CAN'T avoid to fetch informations..
    146 
    147 * `awk` -- a powerfull script language, I don't know enough about this, though
    148 * `cut` -- cut a string in small parts, and pick some parts of it
    149 * `grep` -- should I really present 'grep' ?
    150 * `sed` -- stream editor, it's useful to format outputs
    151 * `test` -- test an expression and return 0 if it's true, >0 otherwise
    152 * `tr` -- translate or delete characters from the input
    153 
    154 By the way, that would be a **HUGE** plus to know about [regular
    155 expressions](https://en.wikipedia.org/wiki/Regular_expression), because we are
    156 going to use them _a lot_ with `sed`.  
    157 So, here we go!
    158 
    159 #### current date / time
    160 
    161 There is nothing hard with it. The `date` utility has a parameter to format its
    162 output. So we'll just use that:
    163 
    164     date '+%Y-%m-%d %H:%M' # print current date and time: yyyy-mm-dd HH:MM
    165 
    166 #### battery level
    167 
    168 There is this tool, `acpi` that can be used to output some infos on your system
    169 power. But that's just not fun! We'll be messing with the `/sys` directory
    170 instead, which is a goldmine. Feel free to navigate it, to see what you can
    171 find.
    172 
    173 Back to the battery. We are interested in two information, the current
    174 charge of the battery (in percent) and if the charger is plugged in, or not. _on
    175 my system_ (because it may be different on yours), I have those two files:
    176 
    177     /sys/class/power_supply/BAT1/capacity   # contains a value from 0 to 100
    178     /sys/class/power_supply/BAT1/status     # either "Charging" or "Discharging"
    179 
    180 We will then be able to output the battery level, and do some action, depending
    181 on the battery state. To get the info:
    182 
    183     BATC=/sys/class/power_supply/BAT1/capacity
    184     BATS=/sys/class/power_supply/BAT1/status
    185 
    186     # prepend percentage with a '+' if charging, '-' otherwise
    187     test "`cat $BATS`" = "Charging" && echo -n '+' || echo -n '-'
    188 
    189     # print out the content (forced myself to use `sed` :P)
    190     sed -n p $BATC
    191 
    192 #### sound level
    193 
    194 This one is always a pain.. I will assume that you use ALSA as your sound
    195 system (because I have no idea how OSS or PulseAudio works).
    196 
    197 First, you need to know which channel your want to watch. Most of the time,
    198 'Master' is a good choice. I personnally use `alsamixer` to navigate between the
    199 channels to see what they are related to. The `alsa-utils` packages (name may
    200 vary depending on the distribution) contains a utility named `amixer` to
    201 interact with the system. The special command `amixer get <CONTROL>` is used to
    202 query informations about a channel. But the output is awful to look at, so we'll
    203 need to format it. Example output:
    204 
    205     ───── amixer get Master
    206     Simple mixer control 'Master',0
    207     Capabilities: pvolume pvolume-joined pswitch pswitch-joined
    208     Playback channels: Mono
    209     Limits: Playback 0 - 64
    210     Mono: Playback 53 [84%] [-10.00dB] [on]
    211 
    212 You can notice that the info we're interested in sits at the end of the output.
    213 That will make things easier.
    214 
    215     # parse amixer output to get ONLY the level. Will output "84%"
    216     # we need `uniq` because on some hardware, The master is listed twice in
    217     # "Front Left" and Front Right" (because laptop speakers I guess)
    218     amixer get Master | sed -n 's/^.*\[\([0-9]\+\)%.*$/\1/p'| uniq
    219 
    220 #### CPU load
    221 
    222 There are many way to get the current CPU load. `iostat` is one of them, and as
    223 it's easy to parse its output, i'll go with a trickier approach, using `ps` and
    224 `bc`.
    225 
    226 To get the current CPU load used by every program, one can use this command:
    227 
    228     # gives you the CPU load used by every running program
    229     # 'args' is used here just so you can see the programs command lines
    230 
    231     ps -eo pcpu,args
    232 
    233 We don't care about idling programs that uses '0.0' load or the header '%CPU',
    234 so we can just remove them with `grep -vE '^\s*(0.0|%CPU)'`.
    235 
    236     ps -eo pcpu | grep -vE '^\s*(0.0|%CPU)'
    237 
    238 We now have a list of the CPU loads actually used, but per program. We just need
    239 to sum them up!  
    240 The problem is: bash _CAN'T_ perform floating point operations. And thus, we
    241 will need the help of the great `bc` to do so (if you don't have this installed,
    242 I recommend that you just get it right away!).
    243 
    244 `bc` takes operations from stdin, and outputs to stdout. Pretty simple. Pretty
    245 good.
    246 Thanks to
    247 [randomcrocodile](http://www.reddit.com/r/unixporn/comments/220diq/howto_create_an_informative_status_bar_for_your/cgi9hve)
    248 for pointing out the two digit problem (and other things)
    249 
    250     # use the "here-line" feature.
    251     # The whole line goes to bc which outputs the result
    252 
    253     LINE=`ps -eo pcpu |grep -vE '^\s*(0.0|%CPU)' |sed -n '1h;$!H;$g;s/\n/ +/gp'`
    254     bc <<< $LINE
    255 
    256 **NOTE**: *verkgw* on irc.blinkenshell.org proposed a faster `awk` alternative.
    257 I don't know awk enough to come up with this kind of line, so I'll just continue
    258 with `grep` and `sed`. See the comparison [here](http://i.imgur.com/Aefbl8U.png)
    259 
    260     ps -eo pcpu | awk 'BEGIN {sum=0.0f} {sum+=$1} END {print sum}'
    261 
    262 #### RAM used
    263 
    264 To display RAM usage (percentage of RAM actually by the system), we will use
    265 another place of the filesystem: `/proc`. This will be easier to find memory
    266 usage here, than battery level in `/sys`:
    267 
    268     ───── ls /proc/ | grep 'mem'
    269     iomem
    270     meminfo
    271 
    272 If you take a quick look at `iomem`, you'll understand that it's **NOT** the
    273 file we want here (I don't understand a bit of it)! Instead, let's take a look
    274 at meminfo:
    275 
    276     ───── sed 8q /proc/meminfo 
    277     MemTotal:        2748648 kB
    278     MemFree:         2209672 kB
    279     Buffers:           34016 kB
    280     Cached:           270728 kB
    281     SwapCached:            0 kB
    282     Active:           182292 kB
    283     Inactive:         272636 kB
    284     Active(anon):     150948 kB
    285 
    286 Good, good, exactly the information we want! So let's just extract them, using
    287 `awk` to fetch _ONLY_ the column containing the value (Yeah, that's why I use
    288 awk for mostly. I'll need to dive a little more in that language):
    289 
    290     ───── grep -E 'Mem(Total|Free)' /proc/meminfo |awk '{print $2}'
    291     2748648
    292     2204288
    293 
    294 At this point, you might realise that those two number are not really useful. We
    295 will need to modify them a little by converting them to Mib, and making a ratio
    296 out of them. A neat alternative would be to ignore cached memory and buffers, to
    297 know exactly how much the applications are taking:
    298 
    299     # store the total and free memory in two variables
    300     read t f <<< `grep -E 'Mem(Total|Free)' /proc/meminfo |awk '{print $2}'`
    301     read b c <<< `grep -E '^(Buffers|Cached)' /proc/meminfo |awk '{print $2}'`
    302 
    303     # then, calcultate the percentage of memory used
    304     bc <<< "100($t -$f -$c -$b) / $t"
    305 
    306 #### network connection state
    307 
    308 Mmh, this one can be tricky! Ther are two cases here:
    309 
    310 * You have one interface
    311 * You have more than one interface
    312 
    313 The first one is quite simple: use your interface name directly, and skip the
    314 following section.
    315 
    316 Now what if you have, let's say two interfaces: ethernet, and wifi. Let's find
    317 out HOW to get the currently used.  
    318 We will need two tools for that: `ip` (from `iproute2`) and `iwconfig` (from
    319 `wireless_tools`). We will get the interfaces with `ip`, and recognize the wifi
    320 interface using `iwconfig`. Sounds easy huh ?
    321 
    322     # The following assumes you have 3 interfaces: loopback, ethernet, wifi
    323     read lo int1 int2 <<< `ip link | sed -n 's/^[0-9]: \(.*\):.*$/\1/p'`
    324 
    325     # iwconfig returns an error code if the interface tested has no wireless
    326     # extensions
    327     if iwconfig $int1 >/dev/null 2>&1; then
    328         wifi=$int1
    329         eth0=$int2
    330     else 
    331         wifi=$int2
    332         eth0=$int1
    333     fi
    334 
    335     # in case you have only one interface, just set it here:
    336     # int=eth0
    337 
    338     # this line will set the variable $int to $eth0 if it's up, and $wifi
    339     # otherwise. I assume that if ethernet is UP, then it has priority over
    340     # wifi. If you have a better idea, please share :)
    341     ip link show $eth0 | grep 'state UP' >/dev/null && int=$eth0 || int=$wifi
    342 
    343 This is now the time to see if network is up or not. For that, a simple `ping`
    344 would do the trick:
    345 
    346     # just output the interface name. Could obviously be done in the 'ping'
    347     # query
    348     echo -n "$int"
    349 
    350     # Send a single packet, to speed up the test. I use google's DNS 8.8.8.8,
    351     # but feel free to use any ip address you want. Be sure to put an IP, not a
    352     # domain name. You'll bypass the DNS resolution that can take some precious
    353     # miliseconds ;)
    354     ping -c 1 8.8.8.8 >/dev/null 2>&1 && echo "connected" || echo "disconnected"
    355 
    356 
    357 #### window manager groups
    358 
    359 Aaah, the information that has the most way to be fetched! The problem with
    360 this, is that every window manager provide a different way to fetch the number
    361 of workspaces, and the current one. If you're lucky, and that your WM is
    362 [EWMH](https://en.wikipedia.org/wiki/EWMH) compliant, `xprop` will be the way to
    363 go. For the others, you will need to find a proper way on your own. For exemple,
    364 to get the number of groups and the current group with ratpoison:
    365 
    366     echo "`ratpoison -c groups| cut -sd'*' -f1`/`ratpoison -c groups| wc -l`"
    367 
    368 Back to the topic, fetching current group out of all the groups. To make this a
    369 little more exiting, we will output something like "`==|=====`", '|' being the
    370 current desktop, '=' being the other desktops.
    371 
    372 The first step is to fetch the number of desktops, and the index of the current
    373 one. To do that, let's use `xprop`
    374 
    375     cur=`xprop -root _NET_CURRENT_DESKTOP | awk '{print $3}'
    376     tot=`xprop -root _NET_NUMBER_OF_DESKTOPS | awk '{print $3}'
    377 
    378 If that enough for you, you can obviously just output `$cur/$tot` ;)  
    379 But now, the desktop indicator. To do that, there is two solutions:
    380 
    381 * cicle through all the groups and output either '=' or '|'
    382 * ouput the correct number of '|' before and after '|'
    383 
    384 I tried both versions, and `time` reports that they are they're _almost_ the
    385 same:
    386 
    387     ───── time cicle.sh
    388     ==|=======
    389 
    390     real    0m0.025s
    391     user    0m0.013s
    392     system  0m0.000s
    393 
    394     ───── time fillup.sh
    395     ==|=======
    396 
    397     real    0m0.020s
    398     user    0m00m0.013s
    399     system  0m0.000s
    400 
    401 We will then use the 'fillup' one. To improve performances, we will first fill a
    402 variable with the 'group line', and then output it. It goes like this:
    403 
    404     # Desktop numbers start at 0. if you want desktop 2 to be in second place,
    405     # start counting from 1 instead of 0. But wou'll lose a group ;)
    406     for w in `seq 0 $((cur - 1))`; do line="${line}="; done
    407 
    408     # enough =, let's print the current desktop
    409     line="${line}|"
    410 
    411     # En then the other groups
    412     for w in `seq $((cur + 2)) $tot`; do line="${line}="; done
    413 
    414     # don't forget to print that line!
    415     echo $line
    416 
    417 #### mpd's current playing song
    418 
    419 After all that we did alredy, printing the current playing should bequite easy
    420 as:
    421 
    422     cur=`mpc current`
    423     test -n "$cur" && echo $cur || echo "- stopped -"
    424 
    425 Easy isn't it ? So let's add some difficulties to it. What if you have only 120
    426 pixels to display that ?  
    427 Aaaah trickyer isn't it ?
    428 
    429 Don't worry, I wrote a small tool for that:
    430 [skroll](http://git.z3bra.org/skroll/log.html). You can see it in action
    431 [here](http://pub.z3bra.org/monochromatic/img/2014-03-28-skroll.gif).
    432 
    433 So now, our output has just become:
    434 
    435     cur=`mpc current`
    436     test -n "$cur" && echo $cur |skroll -n 20 -d0.5 -r || echo "- stopped -"
    437 
    438 A small drawback with this approach: you can't put other infos in the same bar
    439 as a `skroll`ing output, because it uses a `\n` or a `\r` to print the output.
    440 
    441 #### wrap it all !
    442 
    443 Now that we have a whole bunch of informations, it's time to put them all in a
    444 script, that we will pipe later to our HUD.
    445 
    446     #!/bin/sh
    447     #
    448     # z3bra - (c) wtfpl 2014
    449     # Fetch infos on your computer, and print them to stdout every second.
    450 
    451     clock() {
    452         date '+%Y-%m-%d %H:%M'
    453     }
    454 
    455     battery() {
    456         BATC=/sys/class/power_supply/BAT1/capacity
    457         BATS=/sys/class/power_supply/BAT1/status
    458 
    459         test "`cat $BATS`" = "Charging" && echo -n '+' || echo -n '-'
    460 
    461         sed -n p $BATC
    462     }
    463 
    464     volume() {
    465         amixer get Master | sed -n 'N;s/^.*\[\([0-9]\+%\).*$/\1/p'
    466     }
    467 
    468     cpuload() {
    469         LINE=`ps -eo pcpu |grep -vE '^\s*(0.0|%CPU)' |sed -n '1h;$!H;$g;s/\n/ +/gp'`
    470         bc <<< $LINE
    471     }
    472 
    473     memused() {
    474         read t f <<< `grep -E 'Mem(Total|Free)' /proc/meminfo |awk '{print $2}'`
    475         bc <<< "scale=2; 100 - $f / $t * 100" | cut -d. -f1
    476     }
    477 
    478     network() {
    479         read lo int1 int2 <<< `ip link | sed -n 's/^[0-9]: \(.*\):.*$/\1/p'`
    480         if iwconfig $int1 >/dev/null 2>&1; then
    481             wifi=$int1
    482             eth0=$int2
    483         else
    484             wifi=$int2
    485             eth0=$int1
    486         fi
    487         ip link show $eth0 | grep 'state UP' >/dev/null && int=$eth0 ||int=$wifi
    488 
    489         #int=eth0
    490 
    491         ping -c 1 8.8.8.8 >/dev/null 2>&1 && 
    492             echo "$int connected" || echo "$int disconnected"
    493     }
    494 
    495     groups() {
    496         cur=`xprop -root _NET_CURRENT_DESKTOP | awk '{print $3}'`
    497         tot=`xprop -root _NET_NUMBER_OF_DESKTOPS | awk '{print $3}'`
    498 
    499         for w in `seq 0 $((cur - 1))`; do line="${line}="; done
    500         line="${line}|"
    501         for w in `seq $((cur + 2)) $tot`; do line="${line}="; done
    502         echo $line
    503     }
    504 
    505     nowplaying() {
    506         cur=`mpc current`
    507         # this line allow to choose whether the output will scroll or not
    508         test "$1" = "scroll" && PARSER='skroll -n20 -d0.5 -r' || PARSER='cat'
    509         test -n "$cur" && $PARSER <<< $cur || echo "- stopped -"
    510     }
    511 
    512     # This loop will fill a buffer with our infos, and output it to stdout.
    513     while :; do
    514         buf=""
    515         buf="${buf} [$(groups)]   --  "
    516         buf="${buf} CLK: $(clock) -"
    517         buf="${buf} NET: $(network) -"
    518         buf="${buf} CPU: $(cpuload)%% -"
    519         buf="${buf} RAM: $(memused)%% -"
    520         buf="${buf} VOL: $(volume)%%"
    521         buf="${buf} MPD: $(nowplaying)"
    522 
    523         echo $buf
    524         # use `nowplaying scroll` to get a scrolling output!
    525         sleep 1 # The HUD will be updated every second
    526     done
    527 
    528 
    529 All you have to do now is to pipe this script to your status bar of choice:
    530 `./barmk.sh | bar`. 
    531 
    532 There you are! You now know how (ow ow ow) make your system talk to you.
    533 Obviously, this is a raw script, and it can be heavily improved (eg, add some
    534 colors, parse CLI arguments, etc..).
    535 
    536 But I'm pretty sure that it's a good start for your imagination. By the way, if
    537 you find neat tricks to improve the performances of the functions listed above,
    538 feel free to mail me these, I'll be glad to modify them!
    539 
    540 [![barmk showcase](http://pub.z3bra.org/monochromatic/img/thumb/2014-04-02-barmk.png)](http://pub.z3bra.org/monochromatic/img/2014-04-02-barmk.png)
    541 <span class='caption'>Before leaving you, here is what I got using this script
    542 (with some tweaks, see the `diff` output)</span>
    543 
    544 <span class='caption'>Oh, and for reference. It tried to get the same result
    545 with conky and barmk, just to see the difference. Note how conky does not
    546 display float numbers (for CPU). Also, I was not able to recreate a desktop bar with conky, so I "downgraded"
    547 the barmk script to make the battle more fair.</span>[![barmk vs conky](http://pub.z3bra.org/monochromatic/img/thumb/2014-04-10-conky-mkbar.png)](http://pub.z3bra.org/monochromatic/img/2014-04-10-conky-mkbar.png)
    548 
    549 Now go ahead, and watch how your computer tell you how he (or she) feels...
    550 Isn't that **amazing** ?!
    551 
    552 <!-- vim: set ft=markdown ts=4 et tw=80: -->