eigenclass logo
MAIN  Index  Search  Changes  PageRank  Login

Automagic (generalizable) working set discovery, improved ruby-wmii WM scripting 0.2.1

It seems I'm not the only Rubyist who likes the wmii window manager, so I decided to release my wmiirc configuration in Ruby formally. It's cleaner than the original one, and I've added lots of good stuff: view/tag namespaces, new view switching mechanisms, new actions, improved handling of numeric tags (a superset of _why's bindings, can be used with and without numerical tags), better retagging, etc.

And then there's this funny thing nobody would want to implement in shell script :)...

Working set inference using a biased Markov model with exponential decay

I normally have around a dozen open views/tags at a time, so even switching to the Nth one with ALT+N becomes relatively difficult: counting past the 5th view or so requires some thinking. A possible solution can be found on wmii's wiki: a key binding that jumps to the first view starting with the pressed letter. But that won't work very well unless you name your views carefully.

Fortunately, the transitions between views aren't random: they're often predictable, since I tend to stay within a given working set. I came up with a way to discover it automagically.

My first idea was modelling the situation with a first-order Markov process: I assume the probability of jumping to a view given the whole jump history is

P sub { { V sub { n + 1 } } = v sub i} = P left ( { v sub i } left | { V sub 1 ,..., V sub n } right "" right ) = P left ( { v sub i } left | { V sub n } right "" right )

where V sub n is the Nth view I jumped to, and left { v sub i right } is the set of possible destination views. In other words, the view I'm going to jump to depends on which one I'm currently at. The probabilities are updated incrementally as follows: when I've already jumped N times from view v sub j, I assign

P' sub { v sub k left | { v sub j } right "" } = { N } over { N + 1 } P sub { v sub k left | { v sub j } right "" }

and increase the probability for the view I just chose v sub m with

P' sub { v sub m left | { v sub j } right "" } = { 1 } over { N + 1 } + N over { N + 1 } P sub { v sub m left | { v sub j }  right "" }

Favoring the last choice

I modified the basic model to favor recent choices (so it forgets decisions distant in the past), introducing exponential decay and a bias factor alpha for the choice I just made:

P' sub { v sub { k } left | { v sub j } } = alpha { N } over { N + 1 } P sub { v sub k left | { v sub j } }

P' sub { v sub { m } left | { v sub j } } = (1 - alpha ) + { 1 } over { N + 1 } + P' sub { v sub m left | { v sub j } }

alpha = 0 would mean that only the last choice is remembered (all others get probability zero), and alpha = 1 is the normal Markov process. alpha = 0.8 seems reasonable.

Favoring the view I came from

Another refinement consists in biasing for the view I just came from. This is totally outside the Markov model, and I implemented it separately, by assigning a configurable probability to that view before the candidate destinations are sorted.

View intellisort in action

At the end of the day, the list of tags I get with Mod1+t is sorted in such way that the one I want to go to comes normally first, and Mod4+letter takes me to the view I want. I don't know how useful the later will be in practice, though, as it overlaps functionally with Mod1+r (which takes me to the view from which I jumped to the current one). All in all, this was most probably way overdone, but it was fun. Try to write that in shell script someday ;)

Some other goodies

switch keyboard mode (raw/normal)
return to the view I came from (GOOD!)
jump to view starting with letter, uses intellisort
jump to previous/next view within namespace

and many more...

Got Nil - chris (2006-06-22 (Thr) 04:15:53)


wmii-ruby just rocks. Thx, for the great work. I really appreciate it. There's one (minor) issue, though. On one machine the logfile gets filled with "DEBUG -- : Got nil". Any hints how to debug this?

chris 2006-06-22 (Thr) 04:19:29

me, again. Addition: CPU at 100% with 2 ruby processes and log file has several hundert MBs.

mfp 2006-06-22 (Thr) 05:20:58

oops, ugly bug. Does this solve it?

diff -rN -u old-ruby-wmii/wmiirc new-ruby-wmii/wmiirc
--- old-ruby-wmii/wmiirc       2006-06-22 13:08:28.000000000 +0200
+++ new-ruby-wmii/wmiirc       2006-06-22 13:08:28.000000000 +0200
@@ -450,11 +450,12 @@
         system("wmiir -a #{@address} create #{file}")
-      def foreach(file)
-        open("|wmiir read /event") do |is|
+      def foreach(file, &block)
+        open("|wmiir read #{file}") do |is|
+          LOGGER.debug "Executing foreach, using process #{is.pid}"
           $children << is.pid
           is.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
-          loop { yield is.gets }
+          is.each(&block)
@@ -614,9 +615,10 @@
       loop do 
           # wait for events
+          LOGGER.debug "Opening /event"
           @ixp_conn.foreach("/event") do |line|
-              LOGGER.debug "Got #{line.inspect}"
+              LOGGER.debug "Got #{line.inspect}" if line
               case line
               when /^(BarClick|ClientClick)\s+(\S+)\s+(\S+)$/
                 @procs[$1].each{|x| x.call($2, $3.to_i)}

It seems we somehow reached EOF on "|wmiir read /event". My previous implementation was unsafe and stupid :-| Sorry for the mess :/

Wael Nasreddine 2006-06-22 (Thr) 09:18:06

Yes this does solve it, thank you BTW I added some more applets and stuff, most of them came from codemac -> http://bbs.archlinux.org/profile.php?mode=viewprofile&u=3725

My wmiirc -> http://wael.nasreddine.com/files/config/home/wael/.wmii-3/wmiirc

Matt 2006-06-22 (Thr) 09:45:57

[mfp: reformatted]

I had a problem with switching views when there were no numeric tags. My (probably naive) fix is as follows:

--- ruby-wmii-0.2.1/wmiirc      2006-06-21 10:32:39.000000000 +0100
+++ ruby-wmii-0.2.1-new/wmiirc  2006-06-22 17:32:35.000000000 +0100
@@ -276,6 +276,8 @@
       if num_tags.include?(key.to_s)
       elsif key > num_tags.last.to_i
+        # Kludge to fix problem when there are no numeric keys
+        pre = all_views.index(num_tags.last) || -1
         view(all_views[all_views.index(num_tags.last) + (key - 1 % 10) + 1 - num_tags.last.to_i])

Matt 2006-06-22 (Thr) 09:46:29

PS. Sorry about the horrid formatting!

mfp 2006-06-22 (Thr) 10:30:21

Matt: good catch! fixing it... but I'm not sure about doing it exactly that way: I dislike having to rely on NilClass#to_i returning 0.

Wael: I'm working on a way to simplify updates & allow people to publish/exchange extensions easily. This is a fairly interesting problem actually (how to manage sw distribution&deployment, API changes and backwards compatibility in a situation very given to breakage).

mfp 2006-06-22 (Thr) 12:44:48

Matt: I found another bug and ended up applying this

Thu Jun 22 20:32:52 CEST 2006  Mauricio Fernandez <mfp@acm.org>
  * wmiirc: fixed numerical retagging & jumping when there are no numtags. [BUGFIX]
diff -rN -u4 old-ruby-wmii/wmiirc new-ruby-wmii/wmiirc
--- old-ruby-wmii/wmiirc       2006-06-22 20:42:16.000000000 +0200
+++ new-ruby-wmii/wmiirc       2006-06-22 20:42:16.000000000 +0200
@@ -272,12 +272,14 @@
   (0..9).each do |key|
     on_key("MODKEY-#{key}") do 
       all_views = views
       num_tags = all_views.grep(/^\d+$/)
+      nkey = (key - 1) % 10
       if num_tags.include?(key.to_s)
-      elsif key > num_tags.last.to_i
-        view(all_views[all_views.index(num_tags.last) + (key - 1 % 10) + 1 - num_tags.last.to_i])
+      elsif nkey >= (prev_index = (num_tags.last || 0).to_i)
+        non_num_tags = all_views - num_tags
+        view non_num_tags[nkey - prev_index]
@@ -347,13 +349,14 @@
     on_key("MODKEY-Shift-#{key}") do
       all_views = views
       num_tags = all_views.grep(/^\d+$/)
       curr_tags = curr_client_tags
+      nkey = (key - 1) % 10
       if num_tags.include? key.to_s
-        new_tags =  curr_tags.reject{|x| /^\d+$/=~x } + [key]
-      elsif key > num_tags.last.to_i
-        new_tags = all_views[all_views.index(num_tags.last) + 
-                             (key - 1) % 10 + 1 - num_tags.last.to_i]
+        new_tags =  curr_tags.reject{|x| /^\d+$/=~ x } + [key.to_s]
+      elsif nkey >= (prev_index = (num_tags.last || 0).to_i)
+        non_num_tags = all_views - num_tags
+        new_tags = non_num_tags[nkey - prev_index]
       LOGGER.info "Retagging #{curr_tags.inspect} => #{new_tags.inspect}"

I have to find a way to automate the tests! :)

Tom 2006-06-22 (Thr) 18:10:08

If you moved the WMII namespace to a separate file, that might ease things. As it is, it's so long that even my vim syntax highlighting gets lost.

Wael Nasreddine 2006-06-23 (Fri) 03:51:32

There's still some bug somewhere, yesterday my X session ended 3 times while doing some normal stuff, here's tha part of the log -> http://wael.nasreddine.com/trash/weird_crash when it crashed by just closing an urxvt

P.S: can we access your darcs repository (read-only i mean) ??


Wael Nasreddine 2006-06-23 (Fri) 04:04:49

Mauricio I thought of a way to do the extension you said, the file has Two important sections, the bar and the plugins (internal plugins), So instead of filling the wmiirc script with such code, we can create two subdirectories ~/.wmii-3/plugins and ~/.wmii-3/bar, wmiirc script will include all files in those 2 folders, So updating it would be much much easier...

Also I agree with Tom, we should split the wmiirc file into config file and WMII module...

mfp 2006-06-26 (Mon) 04:43:45

Tom, Wael: I implemented the new configuration system over the weekend, you should be receiving an email with the details. To sum up, there are now three parts to ruby-wmii:

  • the main wmiirc, with the core and standard bindings/bar applets. It can be overwritten safely on upgrade.
  • wmiirc-config.rb: contains the user settings, preserved on upgrade
  • ~/.wmii-3/plugins: third party plugins with other bindings/actions and bar applets

Wael: I uploaded a copy of my darcs repository to


As for the crash: the last version uses wmiisetsid to launch external programs; ruby-wmii 0.2.1 didn't and I think that's the culprit of your crash.

Nathan 2006-06-30 (Fri) 16:52:54

[mfp: reformatted]

Just trying out the new goodies now (from darcs). Thanks for writing it :-). Here's a few quick points/questions so far:

  • I think wmiirc needs to set ENV["WMII_FONT"] as well, or wmiimenu will just use fixed all the time.
  • What's with the two modkey thing? Is it the only way to get enough keys for the mod-letter switching? I disabled it for now (although I liked it) since I need mod1 in some apps (I use mod4 for wm functions.)
  • I don't get the raw/normal mode setup... what's the point? (meaning, what am I missing here? :-) )
  • Maybe the volume/mpd/dict etc. bits could be removed from the main wmiirc and made into plugins? It would simplify and shorten the main script, which would make it easier to get started figuring the whole thing out... (you know, for people like me right now)

Anyway, thanks for the great script. I'm sure I'll have more comments sometime, and maybe a plugin or two...

Nathan 2006-06-30 (Fri) 16:56:33

Argh. I've got to get into the habit of using * for lists... sorry about that.

[mfp: np, the benevolent cens^H^H^H^Heditor reformatted it :)]

mfp 2006-07-01 (Sat) 01:11:11

You're very right about WMII_FONT, I'm fixing that. As for MODKEY2, all MODKEY2+letter combinations are taken by the letter-jump-x bindings, leaving few simple sequences free. Note that you could change the key_subs to e.g.

 :MODKEY => Mod4, :MODKEY2 => Mod3

and assign Mod3 to the "Menu" key with

 xmodmap -e "add mod3 = Menu"

if you absolutely want Mod1 to remain free for other apps. OTOH, if only a few Mod1+x bindings interfere with your apps', raw mode might prove useful: it's just a pass-through mode where nothing (but the sequence to get back to normal mode) is captured by wmii. You can also set something like

 key_subs :MODKEY => "Control-t"

which would turn C-t into a sort of WM command prefix.

Anyway, that's just the default configuration and you can rebind everything to your hearts' content in wmiirc-config.rb :)

There's one important practical reason to keep wmiirc's support code and the default configuration in a single file: this way upgrading involves overwriting a single file, and you get both the core upgrades and the fixes to the default actions/applets at once. But you're right, it makes the script harder to read... One solution would be providing better documentation so that there's no need to read wmiirc just to figure how to configure the thing (I'm thinking of adding some mechanism to document plugins). Another would be providing an installation script that copies the standard plugin under ~/.wmii-3/plugins. Hmmm actually I think I'll do that too :)

Celti 2006-07-01 (Sat) 01:18:03

I rather love the modal setup. If I'm using an app with keybindings that conflict with wmii, M2-Space and I'm free to use the app without worrying about making keybindings that don't conflict with a seldom-used application.

I still want an x86 version of the space-cadet keyboard, though. There will never be enough easily-accessible shortcuts to satisfy me.

Nathan 2006-07-01 (Sat) 12:11:10

Ok, now the modal thing makes sense... I think that will work nicely. Thanks.

Oh, and you've got a typo on the last line of use_binding that prevents key overriding from working...

 on_key(*keys, &block)

should be:

 on_key(*actual_keys, &block)

mfp 2006-07-02 (Sun) 01:21:40

Thanks, committed. I also moved the standard plugin to a separate file and wrote install.rb.

mewbda gwrvpnmq 2006-08-07 (Mon) 21:09:09

hjef lygtj dmqlagi uilva ymzgon fldz vjefpqtg

Last modified:2006/06/21 03:56:02
Keyword(s):[blog] [ruby] [frontpage] [wmii] [wm] [script] [configuration] [ruby-wmii] [0.2.1] [release]
References:[ruby-wmii: Ruby configuration/scripting for the wmii window manager]