SelectList 0.3.3

September 30th, 2010

A new minor release of selectList is out, fixing a bug with incorrect handling of special characters in item names. Thanks to Sung Pae for reporting it and suggesting a solution.

Moving Windows to the Current Workspace in Gnome/Compiz

September 23rd, 2010

On my home workstation, I have a pretty vanilla installation of Ubuntu (10.04), with Gnome as my desktop environment, and Compiz. I use four workspaces, and usually have around 50-60 application windows open and scattered around the workspaces. With this many applications, every once in a while I faced the problem of not being able to quickly find the one particular window that I wanted, because it was lost somewhere on one of the workspaces.

I frequently had this issue with KeePassX, a neat password management application. Often, when I wanted to log in to a website/service/whatever, and needed KeePassX to get the login credentials, I had to first go through all the workspaces to find the KeePassX window and drag it to the workspace where I wanted it. This was annoying.

Annoyance is the mother of invention, so I solved this problem with a shell script that locates the application’s window and brings it to the current workspace. If the script finds that the application is not running, it starts it. It takes two arguments, the first argument is a string that is used to locate the “lost” window — the script looks for a window with a title that contains that string. The second argument is the program to run if no window is found (optionally followed by program arguments).

This is how I use it to find/run KeePassX:

summon.sh 'passwords.kdb - KeePassX' keepassx

The KeePassX window title is "/path/to/passwords.kdb - KeePassX", and I use the "passwords.kdb - KeePassX" part as the title string. Just "KeePassX" might be insufficient, if, for instance, there is a web browser window open with the KeePassX homepage and also has "KeePassX" in the title. The "passwords.kdb - KeePassX" string is distinctive enough.

I have this set up as a launcher in my Gnome panel, so I can simply click the KeePassX icon and have the window pop up on the current workspace:

KeePassX Gnome panel launcher

So in case you’d like to use the script for a similar purpose, here it is:

#!/bin/sh

#
# summon.sh - Brings application window to current workspace or starts the
#             application if it isn't running. Intended for use with Gnome and
#             Compiz.
#
# Usage: summon.sh <window title> <program> [arguments]...
#
#        <window title> is the title (or a substring of it) of the window to be
#        found.
#
#        <program> is the application to run if no window is found. May be
#        followed by arguments.
#

SELF=`basename $0`

if [ $# -lt 2 ]; then
    cat <<END
Usage: $SELF <window title> <program> [arguments]...

    Brings application window to current workspace or starts the application if
    it isn't running. Intended for use with Gnome and Compiz.

    <window title> is the title (or a substring of it) of the window to be
    found.

    <program> is the application to run if no window is found. May be followed
    by arguments.
END
    exit 1
fi

TITLE_SUBSTR=$1
shift

# Check if the application is running
WINDOW_ID=`wmctrl -l | grep "$TITLE_SUBSTR" | sed -r 's/\s.*//'`

if [ -z $WINDOW_ID ]; then
    # Not running -- start it
    exec $@
else
    # Application is running -- its window is $WINDOW_ID

    # Get the dimensions of a single workspace
    XDPYINFO_OUT=`xdpyinfo | grep 'dimensions:'`
    WORKSPACE_WIDTH=`echo "$XDPYINFO_OUT" | sed -r 's/.*:\s+([0-9]+)x.*/\1/'`
    WORKSPACE_HEIGHT=`echo "$XDPYINFO_OUT" \
        | sed -r 's/.*:\s+[0-9]+x([0-9]+).*/\1/'`

    # Get the X and Y offset of the current workspace
    XPROP_OUT=`xprop -root -notype _NET_DESKTOP_VIEWPORT`
    CURRENT_X=`echo "$XPROP_OUT" | sed -r 's/.*= ([0-9]+),.*/\1/'`
    CURRENT_Y=`echo "$XPROP_OUT" | sed -r 's/.*= [0-9]+,\s*([0-9]+).*/\1/'`

    # Get the coordinates of the top left corner of KeePassX window
    XWININFO_OUT=`xwininfo -id "$WINDOW_ID"`
    WINDOW_X=`echo "$XWININFO_OUT" | grep 'Absolute upper-left X' \
        | sed -r 's/.*:\s+([0-9-]+).*/\1/'`
    WINDOW_Y=`echo "$XWININFO_OUT" | grep 'Absolute upper-left Y' \
        | sed -r 's/.*:\s+([0-9-]+).*/\1/'`

    # Calculate the new location of the window
    NEW_WINDOW_X=`echo "($CURRENT_X + ($WINDOW_X)) % $WORKSPACE_WIDTH" | bc`
    NEW_WINDOW_Y=`echo "($CURRENT_Y + ($WINDOW_Y)) % $WORKSPACE_HEIGHT" | bc`

    # Move the window to the new location and raise it
    wmctrl -i -r "$WINDOW_ID" -e 10,"$NEW_WINDOW_X","$NEW_WINDOW_Y",-1,-1
    wmctrl -i -R "$WINDOW_ID"
fi

The script uses several command-line tools: wmctrl, xdpyinfo, xprop, xwininfo, and bc. I’m pretty sure they are available by default in a standard Ubuntu installation (and probably most other Linux distros, for that matter). If, however, any of them happen to be missing in your system, install the appropriate packages first.

ImgZoom 0.2.1

September 21st, 2010

I’ve just released a new version of imgZoom. This is a minor update which brings a few bugfixes and small code improvements.

Thanks to Marc Hoyois for his feedback.

Decrypting the User Agent String in JavaScript

September 10th, 2010

I test all my jQuery plugin releases in a simple testing environment that I developed. It’s nothing sophisticated, but it allows me to do much of the testing automatically and generates nice reports. The results of the tests are saved in a database, and for each test there is (among other stuff) information about which plugin version was tested, with which jQuery version, and on what browser.

For that purpose, I wanted to extract the browser name and version number from the user agent string that browsers use to reveal their identity to web pages. However, it’s not that simple, since there is no common format of the user agent string and each browser vendor seems to have their own idea of what to put in it.

For example — which version of Opera is this?

Opera/9.80 (X11; Linux i686; U; en) Presto/2.2.15 Version/10.10


Is it 9.80, or 10.10? If you’re curious, it’s 10.10, and this Dev.Opera blog post explains it.

(If you’re even more curious about why this user agent string business is such a mess, I recommend you read this story — apart from being informative, it’s also funny.)

Anyway, for my testing platform I wrote a JavaScript function that extracts the browser name and version (by default just the major and minor release number) from the user agent string. I’m posting it here in case someone finds it useful too. It recognizes the “big five” browsers that have a significant market share (Firefox, Internet Explorer, Opera, Chrome, and Safari), as these are the browsers that I test my plugins on.

Here’s the source code:

/**
 * Extracts the browser name and version number from user agent string.
 *
 * @param userAgent
 *            The user agent string to parse. If not specified, the contents of
 *            navigator.userAgent are parsed.
 * @param elements
 *            How many elements of the version number should be returned. A
 *            value of 0 means the whole version. If not specified, defaults to
 *            2 (major and minor release number).
 * @return A string containing the browser name and version number, or null if
 *         the user agent string is unknown.
 */
function identifyBrowser(userAgent, elements) {
    var regexps = {
            'Chrome': [ /Chrome\/(\S+)/ ],
            'Firefox': [ /Firefox\/(\S+)/ ],
            'MSIE': [ /MSIE (\S+);/ ],
            'Opera': [
                /Opera\/.*?Version\/(\S+)/,     /* Opera 10 */
                /Opera\/(\S+)/                  /* Opera 9 and older */
            ],
            'Safari': [ /Version\/(\S+).*?Safari\// ]
        },
        re, m, browser, version;

    if (userAgent === undefined)
        userAgent = navigator.userAgent;

    if (elements === undefined)
        elements = 2;
    else if (elements === 0)
        elements = 1337;

    for (browser in regexps)
        while (re = regexps[browser].shift())
            if (m = userAgent.match(re)) {
                version = (m[1].match(new RegExp('[^.]+(?:\.[^.]+){0,' + --elements + '}')))[0];
                return browser + ' ' + version;
            }

    return null;
}

A few examples of user agent strings and the returned results:

Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.9) Gecko/20100825 Ubuntu/9.10 (karmic) Firefox/3.6.9 Firefox 3.6 Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.19 Safari/533.4 Chrome 5.0 Opera/9.80 (X11; Linux i686; U; en) Presto/2.2.15 Version/10.10 Opera 10.10 Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0) MSIE 8.0

ImgZoom 0.2

September 9th, 2010

As promised, I’m releasing imgZoom version 0.2. This is a major update compared to the previous release, as it adds several features that make the plugin an usable alternative to Lightbox (and its numerous clones) — most importantly, the ability to navigate between multiple images (you might have already seen this in the sample gallery that I posted some time ago).

Nonetheless, there are still some features which are obviously missing, like keyboard support and auto-scaling of large images. These are planned for version 0.3, which I hope to develop soon (but, as always, no promises).

The new features are described in the updated documentation. I still need to add some usage examples, though.

As usual, I’m looking forward to your feedback.

ImgZoom 0.2 Almost There

August 28th, 2010

A few months back I announced a new version of imgZoom (the Lightbox-esque image zooming jQuery plugin) and exhibited a sample gallery that showed it in action. Most of the new features were already implemented back then, and I hoped to finish it quickly and release the new version, but then I stumbled over a number of problems with Internet Explorer, got somewhat frustrated, and gave it up for a while.

Now, I have finally found enough inner strength to get back to working on it. For the past few days, I’ve been fighting all those nasty Internet Explorer bugs and its insane behavior. It was even more painful than I expected, as often fixing one problem lead to another, a workaround for IE7 triggered a bug in IE6, and so on.

So far, all the time I spent developing the new version can be accurately represented by this pie chart:

You might notice that it bears a close resemblance to another chart that you might have seen on the interwebs:


(source: EatLiver.com)

Conclusion: screw you, Internet Explorer.

Ok, enough whining. The good news is, it seems I managed to resolve or work around all the problems, and now it’s just a matter of a couple more tests and code cleanups to get the new version ready for release — so expect it in a few days.

Unless, of course, the tests reveal more IE-related problems and I get a nervous breakdown. In that case expect a tweet from a mental institution.

SelectList 0.3.2

August 21st, 2010

I’ve just rolled out a new release of selectList. It fixes a bug observed in IE7/IE8 where the hint text wasn’t shown in the select field as the initial option. Thanks to Nicolas for reporting this.

The new release isn’t available at plugins.jquery.com yet — there seems to be a problem with authentication on their website and I can’t get to the submission form. I’ll upload it there as soon as they get this fixed. For now, you can just get the plugin here.

Edit: Ok, now I know what happened to the plugins website — they had to lock it down temporarily to deal with spam. Get better soon, plugins website!

Twitter Troubles

August 18th, 2010

I started using Twitter a couple months ago. As my numbers of tweets and followers/followees indicate, I’m not using it heavily — I mostly just post announcements on new project releases, blog posts, and my miserable attempts at improving my running performance. Still, I’ve had enough experiences with Twitter to come across a couple irritating problems. Here’s my rant.

First, one day about half of my tweets went missing — they simply disappeared from my timeline. I looked around for an explanation of this problem and found a page at Twitter’s Help Center which convinced me that it was some general issue and a fix was on the way. In the next couple of days the lost tweets came back and everything looked fine.

This mysterious vanishing of tweets happened again a few weeks ago. Additionally, sometimes my timeline displayed the same tweets repeated two or three times (maybe Twitter did this to compensate for the lost ones, I don’t know). This time the situation didn’t get back to normal in the following days, and I decided to report this to Twitter support. On the aforementioned page, the support asked that those suffering from the missing tweets problem leave their details in the comments. So I tried posting a comment — I explained my case, and clicked “Post”. Bzzzt

Sorry, that page doesn't exist!

Apparently, comments weren’t working anymore. So I sent a direct message to @Support, describing my problem (briefly enough to fit in the 140 characters limit). Two weeks passed, no response. I sent another one, still no reply. At this moment, all my tweets from before July are still missing.

Another story. I like reading the technical articles at IBM’s developerWorks, and I found it convenient to follow their Twitter stream and receive brief announcements of new topics. While on my vacation last week, I read a few interesting articles on Android development, and I wanted to read them again now, on my home computer. But, @developerworks posts as many as 20 tweets every day, so last week’s updates were buried deep in the timeline, requiring me to repeatedly click the “More” button and skim through all the tweets to get to the interesting ones.

I hoped the Twitter search feature might allow searching in just my home timeline (the tweets of people that I follow, plus my own), but no, it can only search the whole world. Then I came across search.twitter.com, and I thought that was it — the advanced search options allowed me to limit the search to one Twitter account. I entered “developerworks” and “android” as the search term, and clicked “Search”. Bzzzt — “No results for android from:developerworks”. Weird. I tried another term – “java”, and some results did come up, so the search engine was working. I thought that maybe it only searched the most recent tweets by default, so I went back to the form and set the time range. Clicked “Search”. Bzzzt

The page you were looking for doesn't exist.

After making sure that it was the time range setting that broke the search, I gave up. I went to developerWorks website, entered “android” in their search, and immediately got the articles I was looking for. I guess I should have done that in the first place instead of expecting Twitter to assist me in getting to week-old tweets easily.

I know these issues are just the tip of the iceberg, and many Twitter users have numerous more serious problems. Twitter, you could at least put a big “Beta” (or, even better, “Alpha”) tag on all your pages, so that people know not to expect that the features you offer actually work. Then maybe start fixing things.

By the way, to be sure I’m using the term “timeline” correctly in this post, I checked the Twitter Glossary and clicked the “Find out more about your timeline” link. The result?

Sorry, that page doesn't exist!

Bzzzt.

/me Is Away on Vacation

August 4th, 2010

I’m on a vacation trip to the United States. I finally get to see and experience this exotic country of “America” that everyone’s been talking about so much.

I have a netbook and a smartphone with me and I get online occassionally (like this very moment), so I should be able to check my e-mail every now and then, but don’t get mad if you don’t receive a reply in a timely manner.

By the way, when I boarded the plane I was surprised to be greeted by the Linux penguin and the familiar booting process — apparently the Continental Airlines in-flight entertainment system is running on Linux. Neato.

WebService::Gravatar — Perl interface to Gravatar XML-RPC API

August 3rd, 2010

Developing my first Perl module turned out to be quite amusing, so I was happy to do it again, and here’s my second contribution to the wonderful world of CPAN: WebService::Gravatar, a Perl interface to Gravatar XML-RPC API.

Here’s a quick usage example (taken from the documentation):

    use WebService::Gravatar;
    use MIME::Base64;

    # Create a new instance of WebService::Gravatar
    my $grav = WebService::Gravatar->new(email => 'your@email.address',
                                         apikey => 'your_API_key');

    # Get a list of addresses
    my $addresses = $grav->addresses;

    if (defined $addresses) {
        # Print the userimage URL for each e-mail address
        foreach my $email (keys %$addresses) {
            print $addresses->{$email}->{'userimage_url'} . "\n";
        }
    }
    else {
        # We have a problem
        print STDERR "Error: " . $grav->errstr . "\n";
    }

    # Read image file data
    my $data;
    {
        local $/ = undef;
        open(F, "< my_pretty_face.png");
        $data = <F>;
        close(F);
    }

    # Save the image as a new userimage
    $grav->save_data(data => encode_base64($data), rating => 0);

    ...