Contents

Chapter 1
Introduction

Rumai is a library for manipulating the wmii window manager through Ruby. It excels at dynamic arrangement of clients, columns, views, and tags.

Rumai also provides (1) an interactive shell for live entertainment, and (2) a pure Ruby client for the 9P2000 protocol, which it uses to communicate with wmii’s IXP file-system interface.

Rumai is open-source software (see Section 1.2: License) so feel free to contribute your improvements to and discuss your ideas with the author. You can obtain the source code from the project Darcs repository by running the following command:

darcs get http://rumai.rubyforge.org/src rumai

1.1  Resources

1.2  License

Copyright 2006 Suraj N. Kurapati <SNK at GNA dot ORG>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

  • All copies and substantial portions of the Software (the "Derivatives") and their corresponding machine-readable source code (the "Code") must include the above copyright notice and this permission notice.
  • Upon distribution, the Derivatives must be accompanied either by the Code or—provided that the Code is obtainable for no more than the cost of distribution plus a nominal fee—by information on how to obtain the Code.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Chapter 2
Setup

2.1  Requirements

Your system needs the following software to run Rumai.
Software Notes
Ruby Version 1.8.x is required.
wmii wmii-snap20070516 or newer is required.

2.2  Installation

If your system has RubyGems, then you can install Rumai by running the following commands:

gem install rumai
rumai -v
Otherwise, follow these instructions:
  1. Download the newest release package from the download area.
  2. Extract the release package anywhere you want on your system.
  3. Go inside the extracted directory and run the following command:
ruby bin/rumai -v

If the installation was successful, then you will see output like this:

Rumai 1.0.0 (2008-01-26) http://rumai.rubyforge.org /home/sun/src/rumai

Otherwise, you can e-mail the author (see address in Section 1.2: License) for help.

2.3  Manifest

Now that Rumai is installed on your system, let us examine its installation directory.
  • If you installed Rumai manually, then you already know the location of its installation directory.
  • If you installed Rumai using RubyGems, then run
    rumai -v
    and select the right-most item in the output—that is the path of Rumai’s installation directory.
Inside Rumai’s installation directory, you will see (among other things) the following items:
  • bin/ – contains executable programs.
    • rumai – the interactive shell.
  • lib/
    • rumai.rb – the main Rumai library.
    • rumai/
  • doc/ – contains the user guide and other documentation.
    • guide.erb – the source file of this user guide.
    • api/ – contains API reference documentation.
  • LICENSE – the project license and copyright notice.

2.4  Version numbering system

Rumai uses the RubyGems rational versioning policy to number its releases. This major.minor.patch numbering policy is summarized as follows.
Version number components: Major Minor Patch
Backwards compatible? no yes yes
New features? yes yes no
Bug fixes? yes yes yes

Chapter 3
Usage

3.1  Concepts

If you wish to wield the power of Rumai more effectively, you should understand the following basic concepts:
  • A client is any graphical program that is running in your X session. In other words, any window in your wmii session is a client.
  • A tag is an arbitrary string (a label) that can be associated with one or more clients.
  • A view is a graphical representation of a tag. It shows all clients associated with a particular tag.
  • An area is a region inside a view. It contains clients.
    • A managed area or column is a region where clients cannot overlap each other.
    • The floating area is an area whose clients (1) can overlap each other and (2) float above the managed areas.

3.2  Interactive shell

Run the following command to start Rumai’s interactive shell (IRB):

rumai

You should now see a command prompt like this:

irb(Rumai):001:0>

The “irb(Rumai)” token in the prompt indicates that commands will be evaluated inside the Rumai module. As a result, you can omit the “Rumai” prefix from your commands if you wish.

For example, to get the current client object, you can type curr_client instead of having to type Rumai.curr_client at the prompt. However, note that both commands both achieve the same effect and are thereby equivalent.

The next thing to note is that tab completion is enabled by default. Consequently, you can type part of a command and press the TAB key to see a list of possible completions.

Finally, the interactive shell is a standard IRB session. As a result your ~/.irbrc file is read and processed by default. Furthermore, you can pass the standard IRB command-line options to the rumai command.

3.2.1  Live demonstration

Now that you have a command prompt, let us walk through a quick demonstration that highlights the main features of Rumai. You can follow along by copying & pasting the presented commands into the interactive shell.

3.2.1.1  Automated client arrangement

Launch a few terminals so that we have something to work with:

colors = %w[ red green blue black orange brown gray navy gold ]
colors.each {|c| system "xterm -bg #{c} -title #{c} -e read &" }

Arrange all clients in a grid:

curr_view.arrange_in_grid

Arrange all clients in a diamond shape:

curr_view.arrange_in_diamond

Arrange all clients like LarsWM does:

curr_view.arrange_as_larswm

Close the terminals we launched earlier:

terms = curr_view.clients.select {|c| colors.include? c.label.read }
terms.each {|c| c.ctl.write "kill" }

3.2.1.2  Multiple client grouping

Launch a few terminals so that we have something to work with:

colors = %w[ red green blue black orange brown gray navy gold ]
colors.each {|c| system "xterm -bg #{c} -title #{c} -e read &" }

Add the red, green, and blue terminals into the “grouping”:

terms = curr_view.clients.select {|c| %w[red green blue].include? c.label.read }
terms.each {|c| c.group }

You should now see a new button labelled as “@” on the left-hand side of wmii’s bar, indicating that there is now a new view labelled “@” in wmii. Let us inspect what clients this mysterious view contains:

v = View.new "@"
puts v.clients.map {|c| c.label.read }

Aha! The mysterious view contains the red, green, and blue clients we recently “grouped”. Thus, by adding a client to the “grouping”, we are simply tagging the client with the ”@” token.

Now that we have put some clients into the “grouping”, let us move all clients in the grouping to the floating area in the current view:

grouping.each {|c| c.send "toggle" }

Neat! Let us bring them back into the managed area:

grouping.each {|c| c.send "toggle" }

Close the terminals we launched earlier:

terms = curr_view.clients.select {|c| colors.include? c.label.read }
terms.each {|c| c.ctl.write "kill" }

In summary, you can select multiple clients (by adding them to the “grouping”) and perform operations on them. This is useful when you want to do something with a group of clients but do not want to manually focus one, perform the action, focus the next one, and so on.

Another important aspect is that selected clients stay selected until they are unselected. This allows you to continue performing tasks on the selection without having to reselect the same clients after every operation.

3.2.1.3  Easy column manipulation

Launch a few terminals so that we have something to work with:

colors = %w[ red green blue black orange brown gray navy gold ]
colors.each {|c| system "xterm -bg #{c} -title #{c} -e read &" }

You can insert a group of clients to the top, bottom, or after the currently focused client of any column using Array-like methods.

Give each client its own column (one client per column):

curr_view.each_column {|c| c.length = 1 }

Put (at most) three clients in every column:

curr_view.each_column {|c| c.length = 3 }

Move the red, green, and blue clients into the floating area:

terms = curr_view.clients.select {|c| %w[red green blue].include? c.label.read }
curr_view.areas[0].push terms

Slurp all floating clients into the last column:

list = curr_view.areas
a, b = list.first, list.last
b.concat a

Set the last column’s layout to stacking mode:

b.layout = 'stack'

Move the red, green, and blue clients to the top of the second column:

curr_view.areas[2].unshift terms

Move the red, green, and blue clients to the bottom of the third column:

curr_view.areas[3].push terms

Close the terminals we launched earlier:

terms = curr_view.clients.select {|c| colors.include? c.label.read }
terms.each {|c| c.ctl.write "kill" }

3.2.1.4  Easy client manipulation

Launch a few terminals so that we have something to work with:

colors = %w[ red green blue black orange brown gray navy gold ]
colors.each {|c| system "xterm -bg #{c} -title #{c} -e read &" }

Obtain a reference to the red client:

red = curr_view.clients.find {|c| c.label.read == "red" }

Show the red client’s current tags:

red.tags

Add the “foo” and “bar” tags to the red client:

red.tag "foo", "bar"

Remove the “bar” tag from the red client:

red.untag "bar"

Do complex operations on the red client’s tags:

red.with_tags { concat %w[a b c]; push 'z'; delete 'c' }

Focus the next client after the red client:

red.next.focus ; curr_client == red.next #=> true

Notice that by focusing a client, we make it the current client.

Focus the red client on a different view:

orig = curr_view
v = red.views.last
red.focus v

Return to the original view:

orig.focus

Send the red client to the last column:

red.send curr_view.areas.last

Close the terminals we launched earlier:

terms = curr_view.clients.select {|c| colors.include? c.label.read }
terms.each {|c| c.ctl.write "kill" }

3.2.1.5  Traversing the file system

Show the root node of wmii’s IXP file system:

fs

Show the names of all files at the root level:

fs.entries

Show the parent of the root node:

fs.parent

Show the children of the root node:

fs.children

Navigate into to the /lbar/ directory:

n1 = fs.lbar
n2 = fs['lbar']
n1 == n2 #=> true
left_bar = n1

Notice that you can traverse the file system hierarchy by simply calling methods on node objects. Alternatively, you can traverse by specifying an arbitrary sub-path (relative path) using the [] operator on a node.

Create a new temporary button:

b = left_bar.rumai_example # path of new button
b.exist? #=> false
b.create
b.exist? #=> true

You should now see an empty button on the left-hand side of the wmii bar.

Color the button black-on-white and label it as “hello world”:

content = "#000000 #ffffff #000000 hello world"
b.write content
b.read == content #=> true

Remove the temporary button:

b.remove
b.exist? #=> false

3.2.2  Available commands

The following table lists all methods provided by the Rumai module and its subordinates. See the API documentation for more extensive documentation.
Method declaration Description
Rumai#client_ids()

Returns the IDs of the current set of clients.

Rumai#curr_area()
Rumai#curr_client()
Rumai#curr_tag()

Returns the name of the currently focused tag.

Rumai#curr_view()
Rumai#focus_area(aId)
Rumai#focus_client(aId)
Rumai#focus_view(aId)
Rumai#fs()

Returns the root of IXP file system hierarchy.

Rumai#grouping()

Returns a list of all grouped clients in the currently focused view. If there are no grouped clients, then the currently focused client is returned in the list.

Rumai#next_area()
Rumai#next_client()
Rumai#next_tag()

Returns the name of the next tag.

Rumai#next_view()
Rumai#prev_area()
Rumai#prev_client()
Rumai#prev_tag()

Returns the name of the previous tag.

Rumai#prev_view()
Rumai#tags()

Returns the current set of tags.

Rumai#views()

Returns the current set of views.

Rumai::Area#<<(*aClients)

Alias for push

Rumai::Area#chain()
Rumai::Area#client_ids()

Returns the IDs of the clients in this area.

Rumai::Area#column?()

Checks if this area is a column in the managed area.

Rumai::Area#concat(aArea)

Concatenates the given area to the bottom of this area.

Rumai::Area#ctl_id()

Makes the ID usable in wmii‘s /ctl commands.

Rumai::Area#each(&aBlock)

Iterates through each client in this container.

Rumai::Area#exist?()

Checks if this object exists in the chain.

Rumai::Area#float?()

Checks if this area is the floating area.

Rumai::Area#focus()

Puts focus on this area.

Rumai::Area#fringe()

Returns the next area, which may or may not exist.

Rumai::Area#import_client(c)

Moves the given client into this area.

Rumai::Area#insert(*aClients)

Inserts the given clients after the currently focused client in this area.

Rumai::Area#layout=(aMode)

Sets the layout of clients in this column.

Rumai::Area#length()

Returns the number of clients in this area.

Rumai::Area#length=(aMaxClients)

Ensures that this area has at most the given number of clients. Areas to the right of this one serve as a buffer into which excess clients are evicted and from which deficit clients are imported.

Rumai::Area#push(*aClients)

Inserts the given clients at the bottom of this area.

Rumai::Area#unshift(*aClients)

Inserts the given clients at the top of this area.

Rumai::Area::curr()

Returns the currently focused area.

Rumai::Area::new(aAreaId, aView = View.curr)
aView:the view which contains this area.
Rumai::Chain#chain()

Returns an array of objects related to this one.

Rumai::Chain#next()

Returns the object after this one in the chain.

Rumai::Chain#prev()

Returns the object before this one in the chain.

Rumai::Chain#sibling(aOffset)
Rumai::Client#area(aView = View.curr)

Returns the area that contains this client within the given view.

Rumai::Client#area_to_id(aAreaOrId)
Rumai::Client#chain()

Returns a list of clients in the current view.

Rumai::Client#focus(aView = nil)

Focuses this client within the given view.

Rumai::Client#group()

Adds this client to the current grouping.

Rumai::Client#group?()

Checks if this client is included in the current grouping.

Rumai::Client#send(aAreaOrId, aView = View.curr)

Sends this client to the given destination within the given view.

Rumai::Client#swap(aAreaOrId, aView = View.curr)

Swaps this client with the given destination within the given view.

Rumai::Client#tag(*aTags)

Adds the given tags to this client.

Rumai::Client#tags()

Returns the tags associated with this client.

Rumai::Client#tags=(*aTags)

Modifies the tags associated with this client.

Rumai::Client#toggle_group()

Toggles the presence of this client in the current grouping.

Rumai::Client#ungroup()

Removes this client to the current grouping.

Rumai::Client#untag(*aTags)

Removes the given tags from this client.

Rumai::Client#views()

Returns the views that contain this client.

Rumai::Client#with_tags(&aBlock)

Evaluates the given block within the context of this client‘s list of tags.

Rumai::Client::curr()

Returns the currently focused client.

Rumai::Client::new(aClientId)
Rumai::ClientContainer#client_ids()

Returns the IDs of the clients in this container.

Rumai::ClientContainer#clients()

Returns the clients contained in this container.

Rumai::ClientContainer#grouping()

Returns all grouped clients in this container.

Rumai::Node#[](aSubPath)

Returns the given sub-path as a Node object.

Rumai::Node#children()

Returns all child nodes of this node.

Rumai::Node#clear()

Deletes all child nodes.

Rumai::Node#create(*aArgs)

Creates a file corresponding to this node on the IXP server. See Rumai::IXP::Client#create for details.

Rumai::Node#directory?()

Tests if this node is a directory.

Rumai::Node#each(&aBlock)

Iterates through each child of this directory.

Rumai::Node#each_line(&aBlock) {|line| ... }

Invokes the given block for every line in the content of this node.

Rumai::Node#entries()

Returns the names of all files in this directory.

Rumai::Node#exist?()

Tests if this node exists on the IXP server.

Rumai::Node#method_missing(aMeth, *aArgs)

Provides access to child nodes through method calls.

Rumai::Node#open(aMode = 'r', &aBlock)

Opens this node for I/O access. See Rumai::IXP::Client#open for details.

Rumai::Node#parent()

Returns the parent node of this node.

Rumai::Node#read()

Returns the entire content of this node. See Rumai::IXP::Client#read for details.

Rumai::Node#remove()

Deletes the file corresponding to this node on the IXP server.

Rumai::Node#stat()

Returns file statistics about this node. See Rumai::IXP::Client#stat for details.

Rumai::Node#write(aContent)

Writes the given content to this node.

Rumai::Node::new(aPath)
Rumai::View#area_ids()

Returns the IDs of all areas in this view.

Rumai::View#area_of_client(aClientOrId)

Returns the area which contains the given client in this view.

Rumai::View#areas()

Returns all areas in this view.

Rumai::View#arrange_as_larswm()

Arranges the clients in this view, while maintaining their relative order, in the tiling fashion of LarsWM. Only the first client in the primary column is kept; all others are evicted to the top of the secondary column. Any subsequent columns are squeezed into the bottom of the secondary column.

Rumai::View#arrange_in_diamond()

Arranges the clients in this view, while maintaining their relative order, in a (at best) equilateral triangle. However, the resulting arrangement appears like a diamond because wmii does not waste screen space.

Rumai::View#arrange_in_grid(aMaxClientsPerColumn = nil)

Arranges the clients in this view, while maintaining their relative order, in a (at best) square grid.

Rumai::View#chain()
Rumai::View#client_ids(aAreaId = '\S+')

Returns the IDs of the clients contained in the given area within this view.

Rumai::View#columns()

Returns all columns (managed areas) in this view.

Rumai::View#each(&aBlock)

Iterates through each area in this view.

Rumai::View#each_column(aStartingColumnId = 1) {|a| ... }

Resiliently iterates through possibly destructive changes to each column. That is, if the given block creates new columns, then those will also be processed in the iteration.

Rumai::View#floating_area()

Returns the floating area of this view.

Rumai::View#focus()

Focuses this view.

Rumai::View#manifest()

Returns the manifest of all areas and clients in this view.

Rumai::View#num_managed_clients()

Returns the number of clients in the non-floating areas of this view.

Rumai::View#squeeze(aAreas)

Smashes the given list of areas into the first one. The relative ordering of clients is preserved.

Rumai::View::curr()

Returns the currently focused view.

Rumai::View::new(aViewId)

3.3  Scripting your wmiirc

One important application of Rumai is the support of Ruby-based wmiirc configuration files. For a solid example of such application, take a look at my personal wmiirc which uses Rumai extensively:

darcs get http://snk.tuxfamily.org/src/wmiirc

Alternatively, you can browse the contents of the above repository in your web browser.