wmii+node.js, Part 1: Event Handling

This post is part of a series.

Previous post: << Introduction

Next post: Keyboard Events >>

Before I start writing code, here’s some information that will probably be useful:

  • I’m using the latest hg version of wmii. This is different from the wmii that is provided with most Linux distributions. Its config file format is different, and it fixes several bugs present in previous versions. You will probably need to compile it yourself (along with libixp, which it depends on).
  • I’m running Ubuntu 13.04. This will probably work the same on any other Linux distro, though.
  • I plan to model this config after my old bash wmii config. Although it didn’t turn out the best (mainly because bash is a terrible language to work with), I like the modular design, and want to implement a more stable version in node.js.

 

If you haven’t used wmii before, you may want to compile it and try it out with the built-in config first, and read the man page. I’ll explain a lot about wmii’s internals and APIs in this blog, but I’m assuming that anyone reading this already knows the basics of how to use wmii. The first thing I did when approaching this project was to read up on node.js. In order to make this project work, there are 3 parts of node.js that I need to understand thoroughly:

  • Launching external programs
  • Module management (importing other .js files)
  • Event handling and custom events

Luckily, as it turns out, all of these are really simple in Node. Let’s get the basics out of the way and set up Node first. In Ubuntu, you can just sudo apt-get install nodejs npm to install Node and its package manager, npm. For some reason, in Ubuntu the executable for Node is called nodejs instead of node; I created a symlink to fix this. Next, write the classic introductory program:

Save it as hello.js, and run it with node hello.js. Nice and simple.

 

Events and wmiir

Now let’s do something more interesting: reading events from wmii. In order to do this, we’ll need to launch wmiir from Node and read its output. Looking through Node’s documentation shows that I can use child_process.spawn to launch an external program, and that I can then add an event listener to its stdout stream object to run a callback function every time it reads… some amount… of data from the stream. It’s not quite clear how much. So we’ll find out by experiment. This sample program should print wmii events to the console as they occur:

Save it as event_reader.js and run it. Note that from here on in, I’m assuming that you’re running wmii. This code won’t work unless wmii is running! Once the program is started, move your mouse around to switch focus between a few different windows. You should see the events start appearing on the console, like this:

Got event: ClientFocus 0x1285e8e

Got event: AreaFocus 1

Got event: ClientFocus 0xe0000a

Got event: AreaFocus 2

 

At first, this looks like Node is reading data one line at a time, but without removing the trailing newlines. I thought this at first, too. Actually, it’s reading data in completely arbitrary “chunks”; they only happen to correspond to lines here because wmii is writing events to the file one line at a time. You can see what’s going on if you replace ‘/event’ with ‘/ctl’ (a settings file with an actual EOF) and run the script again:

Got event: bar on bottom
border 1
colmode stack
focuscolors #000000 #81654f #000000
font fixed
fontpad 0 0 0 0
grabmod 
incmode squeeze
normcolors #000000 #c1c48b #81654f
view 1

 

This time, it read the whole file as a single chunk. This behavior will make reading the /events file line-by-line somewhat tricky, because the callback isn’t guaranteed to receive a line at a time. Because of idiosyncrasies like this, and because calling child_process.spawn is the main way that we’re going to interact with wmii, it makes sense to encapsulate the whole process of talking to wmiir in a few convenience functions.

 

wmiir.js

In fact, these functions would make a good Node module. A module in Node is any JS file that defines an object called exports. Whenever you call require in another file, the require‘d file is executed if it hasn’t been before (otherwise, a cached copy is returned–keep that in mind; it’s important!), and its exports object is returned to the file that called require.

wmiir provides several subcommands, but the most commonly used ones are readwrite, ls, create, and remove. Read man wmiir for a description of how these work; wmii’s manpages are the only reliable documentation available since so much information about wmii on the web is outdated. Creating a wrapper function for each subcommand seems like a good approach. Here are (thoroughly-commented) wrapper functions for the read and write commands:

The full wmiir.js file is on GitHub as well, but the snippet above should be enough to understand what’s going on. Each function calls an external command, then passes its results to a callback. According to Node convention, callbacks should reserve their first parameter for any errors thrown by the caller. Node’s callback-based I/O solves the two biggest problems with my bash config: it prevents the external programs from blocking the event loop, and it doesn’t require forking a bunch of extra listener processes. Node runs in a single thread, and, every time an event occurs, any callbacks that it triggers are added to a queue. When the interpreter runs out of code to execute, it just takes the next callback off of the queue and runs it, or waits for one to show up. This is much more stable and efficient than multithreaded or multiprocess systems, especially for simple tasks.

 

wmii_events.js

Now that we have a wmiir module, let’s create an event handler module. Node already has a concept of event handlers, so, ideally, our wmiir event handler should behave exactly like other eventful Node objects. The Node documentation says that this can be done by extending the EventEmitter class. JavaScript classes and inheritance are complex enough to deserve a blog post of their own, so I won’t describe how they work here, but it’s worth noting that Node provides a convenience function, util.inherits, for creating class constructors that inherit from other classes.

For this module, we’ll require our existing wmiir.js module (which should be in the same directory), and export an EventEmitter instance. We’ll also override EventEmitter.emit, to send a wmii event through the /event file, rather than just sending a Node event.

Notice that this time, rather than adding new functions to module.exports, we replaced it with an entirely new object. (exports is just an alias for module.exports). Whenever another file requires this file, it will start the event loop automatically–and, because require caches already-loaded files, we can use this event loop in as many other files as we want, and still be guaranteed that only one instance is running! Much nicer than bash.

 

Putting it all Together

Finally, let’s write a sample program that uses our event loop module. This program will listen for the ‘Key’ event, which happens whenever the user presses a key that wmii has bound, and print the name of the key.

Save this file and run it (in the same folder as the other files), and then press some keys to navigate around wmii (like Mod4+hjkl, or Mod4+arrow keys if you’re like me and still use the arrow keys for everything). You should get output like this:

Keypress: Mod4-Left
Keypress: Mod4-Right
Keypress: Mod4-Left
Keypress: Mod4-Right

 

Key events are only sent for bound keys, so pressing random keys won’t output anything; you have to press keys that wmii is actually using. In my next post, I’ll look at handling wmii keybindings and key events.

Next post: Keyboard Events >>

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s