Confessions of a Window Shader

I’ve been a window shader for a very long time. For those that don’t know, window shading causes the main body of a window to disappear, leaving just the titlebar in place. It’s an alternative to minimizing (GNOME 2, Windows), iconifying (OS X), and hiding (GNOME 3). I shade windows by double-clicking the titlebar. There’s a hidden option to enable this in GNOME 2.

Window shading has been around for a long time. It’s not even exclusive to *nix desktops. It was used in pre-X versions of MacOS. I seem to be one of the few remaining window shaders in the modern desktop world, so I want to share my experience. I’m not trying to debate what’s better, and I’m certainly not demanding that designers cater to my whims. I’m just sharing how I use my desktop.

I have no problems with overlapping windows. In fact, I rely on them. I often read something in one window while writing something in another. Shifting my gaze is much faster than changing windows in a window list. This is important to me, because I do it a lot. I have terrible short-term memory. I can tell you what lot we parked in at Disney World when I was 8, but I can’t tell you what I read five minutes ago. Consequently, I almost never maximize windows. I confess, I really don’t understand maximizing windows on large, wide-screen monitors. (Mine is 1680×1050.) It’s too wide to read. My windows are at their natural size, and that size depends on what’s in them.

But when you have lots of small, overlapping windows, it’s easy to lose track of them. I have a fairly static arrangement of windows. I know my web browser and other document windows cascade from the top left. I know my terminals are flush bottoom. I know my smaller utility windows are somewhere in the middle of the screen. I don’t really think about where the windows are, because I have years of habit ingrained in me. Habit helps me.

When I need to get behind a window, or when I don’t want to look at it, I shade it. I could just as easily minimize it to get it out of my way. There’s no practical difference in the hiding act. The difference comes in when I want that window back. And more often than not, I want it back soon.

Window lists and zoom overviews have little to no spatial resemblence to how your windows are (or were) arranged. But shading leaves your windows where they were, just smaller. Because I have strong window placement habits, I don’t have to hunt for the window I want in some arbitrary list or grid. I go to where the window is. It’s in the same place as I left it. And as a result, I spend almost no time looking for windows.

I’ll probably have to change my habits soon, because modern desktops are moving away from supporting this kind of feature. But with all the innovation that’s happening, I’m hopeful that somebody will come up with a new way of bringing back hidden windows that actually takes advantage of spatial window placement habits.

Is Your Community a Community?

There’s a lot of buzz in the tech world around the word community. Perhaps stemming from the wrench that social media has thrown into the works, business folks have started tossing around terms like community content, community relations, and community management. But do they know what community means?

“You keep using that word. I do not think it means what you think it means.”

I’ve been a community leader in the open source world for the last eight years. Before that, I was a community member. You cannot be a community leader without being a community member. To understand what a community leader does, you have to understand what a community is.

  1. A community is self-governing. Community members are empowered to decide the direction of the community. If you’re telling people what to do and how to do it, you don’t have a community. You have a workforce.
  2. A community is social. I don’t mean it’s on Facebook or Twitter. Community members might use Facebook or Twitter, but they might use something old-school like mailing lists or IRC. What’s important is that people are talking to each other, getting to know each other, and making decisions together. Without social interaction, you just have a random group of contributors.
  3. A community has a common interest. Community members will have differences on many topics, but there has to be something that brings and keeps them together.

Community leaders don’t set direction or dictate what gets done. That would work against the self-governing nature of communities. And they don’t keep projects or priorities secret. That would be anti-social. So what does a community leader do?

  1. The first job of a community leader is to welcome new members. People will come and go from your community. That’s the nature of things. A lot of people will just stop by because they’re curious. If you don’t welcome them and give them a way to get involved, they’ll be gone before they’ve ever started.
  2. A community leader keeps things running smoothly. If your community is trying to get things done (and when businesses talk about communities, they want those communities to do something), then someone needs to stay on top of those things. You can’t make community members do anything. But you can keep track of what they’ve decided to do and remind them. Leaders are like personal assistants to the entire community.
  3. A community leader keeps the big picture in mind. You have to have your finger on that common interest that’s keeping your community together. When people get bogged down with the trees, you have to remind them of the forest.

Every successful community I know of works this way. And every time a company tries to set up a community, but ignores the nature of communities, it eventually fails. So, do you have a real community, or are you just treating your users like employees?

Developer Documentation Hackfest

I’m looking for a few talented hackers/writers to help us work on the developer documentation for the Gnome platform in Toronto March 17-22. We’re already hosting a hackfest for user help, and we’d like to colocate a developer documentation hackfest there. We’ll be focusing on the Platform Overview and the new Developer Demos. Time permitting, we can also put some extra polish into our (already mostly comprehensive) API references.

Here’s the skills we’re looking for:

  • Familiarity with the Gnome platform. You don’t have to be a Matthias Clasen or an Owen Taylor. In fact, knowing too much can sometimes cause you to overlook what new developers need to know. But you do have to have some experience building applications with Gnome.
  • Some experience writing XML markup. The Overview and Demos are in Mallard. The API references are done with gtk-doc, often with inline DocBook. You don’t need to be an expert on any of these. If you know XML and are comfortable writing it in a text editor, the details aren’t hard to pick up.
  • Patience and a willingness to learn. Effective writing is more than just dumping what you know into a text editor. We’ll have experienced technical writers on hand who will help you plan and organize your material.

If you’re interested, email me at shaunm@gnome.org.

A Quick Primer on Blip Plugins

The nice thing about Blip is that it’s completely open and insanely extensible. It tries very hard to find things like documents and translations wherever it can, but sometimes your content just doesn’t look like what it knows about.

But that’s OK. You can easily add plugins to find and display more information. In fact, everything you see in Blip—every bit of data collected and every page generated—comes from a plugin. Some of them are in the core blip package. Others are provided by the blip-gnome package, which has plugins to scan modules that look like typical Gnome modules.

If you want to deploy Blip for your project, you can create a package full of plugins to deal with the kind of data you actually care about. To show how easy this is, I’ll walk through a plugin I just added for the Evolution Quick Reference Card. This is a PDF document generated from a TeX file that lists common keyboard shortcuts. Obviously, this won’t be picked up by the plugins that handle DocBook, Mallard, and gtk-doc documents.

First, look at the report on the Evolution Quick Reference Card on Blip. Notice that Blip knows the title of the document, when it was modified, and what translations exist.

All of this comes from the evolutionquickref Blip plugin. This is less than 100 actual lines of code. Let’s break it down.

class QuickRefScanner (blip.plugins.modules.sweep.ModuleFileScanner):

In Blip, you create plugins by subclassing known extension points. In this case, there’s a module scanner (which is itself just a plugin). The module scanner reads the SCM history for a module, then walks the tree of files calling each file scanner.

A file scanner contains two methods. The process_file method is called for every file in the module. It’s expected to decide for itself whether it’s interested in the file. After all the files have been walked, the post_process method is called on each file scanner. Sometimes you need to find everything before you can fully process stuff.

    def process_file (self, dirname, basename):
        branch = self.scanner.branch
        is_quickref = False
        if branch.scm_server == 'git://git.gnome.org/' and branch.scm_module == 'evolution':
            if basename == 'Makefile.am':
                if os.path.join (self.scanner.repository.directory, 'help/quickref') == dirname:
                    is_quickref = True
        if not is_quickref:
            return

Here we’re just determining whether we care about the file. Unlike most scanners, we hardcode the SCM server and module to restrict it to Evolution. We then only look at help/quickref/Makefile.am. Why that file? Because we’re going to parse it for language information.

        with blip.db.Timestamp.stamped (filename, self.scanner.repository) as stamp:
            try:
                stamp.check (self.scanner.request.get_tool_option ('timestamps'))
            except:
                data = {'parent' : branch}
                scm_dir, scm_file = os.path.split (rel_ch)
                data['scm_dir'] = os.path.join (scm_dir, 'C')
                doc = blip.db.Branch.select_one (type=u'Document', **data)
                if doc is not None:
                    self.scanner.add_child (doc)
                    self.document = doc
                raise

Blip aggressively timestamps to avoid needless reprocessing. Trust me, in a production environment, you do not want it reprocessing every file from 100+ modules all the time.

Blip provides a convenient timestamp-checking class you can use with Python’s awesome with statement. When you call stamp.check, it checks the mtime of the file against what’s recorded in the database. If the file is newer, or if the database has no timestamp, it just returns and lets your code execute. Otherwise, it throws an exception that’s caught by the with block. If the entire block finishes without exception, a timestamp is added to the database automatically.

(By the way, Blip uses introspection to automatically attach a source function to each timestamp. So this timestamp won’t interfere with timestamps on the same file from another plugin.)

Sometimes you still want to do things when you’re skipping a timestamped file. In this case, we try to find a matching document in the database and add it to the module scanner. Why do we do this? The module scanner deletes child objects (documents, applications, translation domains, etc) from the database unless it’s told about them on each processing run. This is how we prevent stale information from lingering around in Blip. But that means we need to let the module scanner know about the document, even if we’re not going to process it.

            makefile = blip.parsers.get_parsed_file (blip.parsers.automake.Automake,
                                                     self.scanner.branch, filename)

Blip has some built-in parsers for the kinds of files it usually encounters. This one tries to extract some basic information from Makefile.am files. It can’t do everything; after all, an automake file is a template for a shell script. But it does a good job with most data.

            ident = u'/'.join(['/doc', bserver, bmodule, 'quickref', bbranch])
            document = blip.db.Branch.get_or_create (ident, u'Document')
            document.parent = branch
            for key in ('scm_type', 'scm_server', 'scm_module', 'scm_branch', 'scm_path'):
                setattr (document, key, getattr (branch, key))
            document.subtype = u'evolutionquickref'
            document.scm_dir = blip.utils.relative_path (os.path.join (dirname, u'C'),
                                                         self.scanner.repository.directory)
            document.scm_file = u'quickref.tex'
            self.scanner.add_child (document)
            self.document = document

We first create an ident. This is how just about everything is identified in Blip. In many cases, idents match the path of the URL you use to access objects. We use some information from the ident of the parent module. We then get or create an object in the database. If it’s not there already, Blip just makes it for us, and handles some extra administrative work.

After setting some information, we add the document to the module scanner. This is the same thing we did in the except clause above. We have to add it, or it will be deleted.

            translations = []
            for lang in makefile['SUBDIRS'].split():
                if lang == 'C':
                    continue
                lident = u'/l10n/' + lang + document.ident
                translation = blip.db.Branch.get_or_create (lident, u'Translation')
                translations.append (translation)
                for key in ('scm_type', 'scm_server', 'scm_module', 'scm_branch', 'scm_path'):
                    setattr (translation, key, getattr (document, key))
                translation.scm_dir = blip.utils.relative_path (os.path.join (dirname, lang),
                                                                self.scanner.repository.directory)
                translation.scm_file = u'quickref.tex'
                translation.parent = document
            document.set_children (u'Translation', translations)

We now find translations by looking at built subdirectories (except C, because that’s the source). We go through the same business of creating idents and getting or creating database objects.

Then we call set_children on the document. Remember that business of having to add children to the module scanner to keep them from being deleted? This is exactly what the module scanner calls on the module, using the objects that get added to it. This one line prevents us from having stale translations in the database if they’re removed from the module.

We’ll handle everything else in post_process.

        regexp = re.compile ('\\s*\\\\textbf{\\\\Huge{(.*)}}')
        filename = os.path.join (self.scanner.repository.directory,
                                 self.document.scm_dir, self.document.scm_file)
        with blip.db.Timestamp.stamped (filename, self.scanner.repository) as stamp:
            stamp.check (self.scanner.request.get_tool_option ('timestamps'))
            stamp.log ()
            for line in open(filename):
                match = regexp.match (line)
                if match:
                    self.document.name = blip.utils.utf8dec (match.group(1))
                    break

Here we process the quickref.tex file. (That’s what we set scm_file to above.) Again, we do this behind a timestamp so we don’t read a file if it hasn’t changed. We just loop over the lines in the file looking for one that matches our regexp, and use that to extract the title of the reference card. This is a one-off plugin, so we could have just hardcoded the title. But what’s the fun in that?

        rev = blip.db.Revision.get_last_revision (branch=self.document.parent,
                                                  files=[os.path.join (self.document.scm_dir,
                                                                       self.document.scm_file)])
        if rev is not None:
            self.document.mod_datetime = rev.datetime
            self.document.mod_person = rev.person
        self.document.updated = datetime.datetime.utcnow ()

Now we look up the most recent git revision to have touched quickref.tex and record that information. “But Shaun,” you say, “that information is already in the database. Normalize!” I’m not dogmatic about normalization. I put a lot of effort into fast page load times (and there’s still work to be done on some pages). This helps a lot.

We also set the updated field. This just says when Blip last looked at this particular thing. After that (code not reproduced here), we loop over the translations and do the same thing for them.

That’s it. A simple but non-trivial Blip plugin in about 100 lines of code. If you’re feeling adventurous, you could try to compute completion statistics for the translations. They’re not using PO files, but the TeX files are predictable and parseable, so you could put some sort of statistics together.

What kind of data do you have in your modules? As long as it’s parseable, it’s as simple is this to tell Blip about it. Try it yourself. And get in touch; I’m happy to help people build on Blip.

Open Help Conference

Creative Commons Attribution 3.0 United States
This work by Shaun McCance is licensed under a Creative Commons Attribution 3.0 United States.