Using the Unity Web API

Posted by Nathan Osman on July 21, 2012

One of the exciting new features scheduled to be included in Ubuntu 12.10 (Quantal Quetzal) is the introduction of Web Apps. These consist of a UserScript that bridges the gap between the JavaScript code on the page and the user’s desktop. For example, a script written for Gmail might display a desktop notification when a new message arrives in the user’s inbox. Or perhaps a script written for MySpace would make use of the desktop media player interface to display Title / Artist / Track information for currently playing music.

The wealth of functionality that these new APIs provide open up a wide range of possibilities. The current list of Web Apps with scripts can be viewed here. In fact, I encourage you to click on one of the scripts to get an idea of how it works.

Before I begin explaining the process of writing a script for the API, I should point out that documentation and a simple tutorial already exist. However, the tutorial doesn’t explain some of the details for setting up the script and what you should do with it after you have finished writing it.

Overview

I have always found that the best way to describe a process to someone is by walking them through it. Therefore, I will explain in details the steps I have taken to write a script for Ask Ubuntu. I encourage you to check out the site if you haven’t already to get an idea of how we can best integrate some of the site’s features into the user’s desktop.

First Things First

The very first thing you need to do is to add the webapps PPA to your system and install the unity-webapps-preview package:

sudo add-apt-repository ppa:webapps/preview
sudo apt-get update
sudo apt-get install unity-webapps-preview

After running these commands in a terminal, you will need to log out and in again. Now open Chromium and visit Launchpad. You should see a small bar along the top of the page asking if you want to integrate Launchpad with your desktop.

If you click “Yes”, you will end up with an extra icon in the Launcher among other things:

Try visiting a page on Launchpad with a list of bugs for a project. Open the HUD and start typing part of the title for one of the bugs. Notice that the bugs show up in the list and you can click on them to view more details.

Checking Out the Source

To develop a script for your favorite website, you must first check out a copy of the webapps-applications branch. If you have Bazaar installed, you can simply run the following commands:

mkdir webapps-applications
cd webapps-applications
bzr init
bzr pull lp:webapps-applications

If you do not have Bazaar installed, you will need to run the following command followed by the ones above:

sudo apt-get install bzr

You should now have the latest code from the webapps-applications branch.

Creating the Icons

The next step is to create an icon for the Launcher. In our case, the icon exists in SVG format here. We need to create four images from the SVG file in the following dimensions: 128x128, 64x64, 52x52, and 48x48. The Gimp is an excellent tool for this task. Now open the directory you created in the previous step (the webapps-applications branch) and open the icon-themes/unity-webapps-applications/apps directory. You will see four directories with names that correspond with the four dimensions listed above. Copy each image to the appropriate directory, ensuring they have the same filename (‘askubuntu.png’ for example).

Now open a terminal and browse to the ‘icon-themes’ directory. Run the following command:

sh build-icon-list.sh

Beginning the Script

We are now ready to begin writing the actual script itself. Open up a text editor and enter the following:

// ==UserScript==
// @name           askubuntu-unity-integration
// @include        http://askubuntu.com/*
// @version        1.0
// @author         [Your Name]
// @require        utils.js
// ==/UserScript==

Anyone familiar with JavaScript programming will recognize what we just typed as a set of comments. Anyone familiar with writing UserScripts will recognize that this is a special comment block. It provides some basic details about the script. Of particular importance is the @include line. This should be a wildcard URL for which this script will be run. In our case, we want it run on every page on the Ask Ubuntu website. Later on, we’ll examine ways we can limit the script’s actions to particular pages.

Now you need to save the script. Browse to the ‘src’ directory of the branch and name the file ‘AskUbuntu.user.js.in’. The extension is important.

Building the Script

In order for your script to be included with the others when the project is built, you will need to add it to the Makefile. Open the file ‘src/Makefile.am’ and locate the ‘EXTRA_DIST’ list. Scroll to the bottom of the list, append a backslash to the last item on the list, and then insert a new line containing the name of the file from the previous step (being sure to maintain the same level of indentation).

If what I just described was confusing and hard to follow, this snippet should clear things up:

...
Tumblr.user.js.in\
AskUbuntu.user.js.in

Now do the same thing for the ‘user_scripts’ list only omit the ‘.in’ at the end of the filename:

...
Tumblr.user.js\
AskUbuntu.user.js

Now we can finally build everything. Open a terminal to the directory containing the branch and run the following three commands:

./autogen.sh
make
sudo make install prefix=/usr

If you get any errors from running the first command, you are probably missing some development packages (which you can easily install with apt-get). The three commands above will be used every time we make changes to our script and want to test it out.

Making the Script Functional

As it stands now the script doesn’t do anything. Add the following lines to the end of the script:

window.Unity = external.getUnityObject(1);
Unity.init({ name:    'Ask Ubuntu',
             iconUrl: 'icon://askubuntu',
             onInit:  null });

Now try building the script and running it. As soon as you visit the Ask Ubuntu home page, you should receive the usual prompt. If you select “Yes”, you should see a new icon on your launcher for Ask Ubuntu. It still doesn’t do anything much - but we’re making progress.

Actually Making the Script Functional

Let’s add some entries to the HUD. We want to add these entries when the script is initialized - so we will write a function that creates the entries and we will pass the function as the ‘onInit’ parameter to the Unity.Init() call. Here is what our initialization function looks like:

function unityLoaded() {
    Unity.addAction('/Ask Question', makeRedirector('http://askubuntu.com/questions/ask'));
}

Now modify the Unity.init() call so that the third line becomes:

onInit: wrapCallback(unityLoaded) });

Try rebuilding the script and running it again. Open the HUD and begin typing “ask”. Our new addition should show up in the list.

Limiting the Script to Certain Pages

Sometimes we only want the script to execute on certain pages. Unfortunately the logic for determining whether the script should run on a certain page usually exceeds what can reasonably be expressed in a wildcard URL. Therefore we must perform the check ourselves. Consider the following function:

function isCorrectPage() {
    return location.pathname.match(/^\/questions/);
}

This function will check to see if the current page URL begins with “/questions” and can be invoked as follows:

if(isCorrectPage())
    Unity.init(...);

The Launcher icon will only be displayed if the page URL begins with “/questions”. Of course you aren’t limited to using regular expressions for checking the current page. A number of scripts use XPath to look for items in the page’s DOM - a much more flexible approach.

The Final Product

Here is what our script looks like in its entirety:

// ==UserScript==
// @name           askubuntu-unity-integration
// @include        http://askubuntu.com/*
// @version        1.0
// @author         [Your Name]
// @require        utils.js
// ==/UserScript==

window.Unity = external.getUnityObject(1);

function isCorrectPage() {
    return location.pathname.match(/^\/questions/);
}

function unityLoaded() {
    Unity.addAction('/Ask Question', makeRedirector('http://askubuntu.com/questions/ask'));
}

if(isCorrectPage())
    Unity.init({ name:    'Ask Ubuntu',
                 iconUrl: 'icon://askubuntu',
                 onInit:  wrapCallback(unityLoaded) });

Now What?

Once you have finished writing your script, you need to write tests for it. Unfortunately I don’t have the time to explain how this works. However, the branch you checked out contains a file ‘doc/walkthrough.txt’ which briefly explains how this is done.

After writing tests, you can commit the changes to the branch you checked out:

bzr commit -m "Created a script for Ask Ubuntu, added icons, and wrote tests for the script."

Assuming you have a Launchpad account (and pretending your LP username is ‘walter’) you can push the code to a separate branch:

bzr push lp:~walter/webapps-applications/askubuntu

You can now propose the branch for merging.