I’ve finally had a little extra time to get back to working on Singlet. There’s been a lot of progress since the first iteration. To start with, Singlet had to be upgraded to work with the new Lens API introduced when Unity 5.0 landed in the Precise repos. Luckily the Singlet API didn’t need to change, so any Singlet lenses written for Oneiric and Unity 4 will only need the latest Singlet to work in Precise.
The more exciting development, though, is that Singlet 0.2 introduces an API for Scopes. This means you can write Lenses that support external scopes from other authors, as well as external Scopes for existing lenses. They don’t both need to be based on Singlet either, you can write a Singlet scope for the Music Lens if you wanted to, and non-Singlet scopes can be written for your Singlet lens. They don’t even have to be in Python.
In order to make the Scope API, I chose to convert my previous LoCo Teams Portal lens into a generic Community lens and separate LoCo Teams scope. The Lens itself ends up being about as simple as can be:
from singlet.lens import Lens, IconViewCategory, ListViewCategory
name = 'community'
description = 'Ubuntu Community Lens'
search_hint = 'Search the Ubuntu Community'
icon = 'community.svg'
category_order = ['teams', 'news', 'events', 'meetings']
teams = IconViewCategory("Teams", 'ubuntu-logo')
news = ListViewCategory("News", 'news-feed')
events = ListViewCategory("Events", 'calendar')
meetings = ListViewCategory("Meetings", 'applications-chat')
As you can see, it’s really nothing more that some meta-data and the categories. All the real work happens in the scope:
name = 'locoteams'
search_hint = 'Search LoCo Teams'
search_on_blank = True
lens = 'community'
categories = ['teams', 'news', 'events', 'meetings']
def __init__(self, *args, **kargs):
super(LocoTeamsScope, self).__init__(*args, **kargs)
self._ltp = locodir.LocoDirectory()
self.lpusername = None
import ConfigParser as configparser
bzrconf = configparser.ConfigParser()
self.lpusername = bzrconf.get('DEFAULT', 'launchpad_username')
def search(self, search, model, cancellable):
I left out the actual search code, because it’s rather long and most of it isn’t important when talking about Singlet itself. Just like the Lens API, a Singlet Scope uses an inner Meta class for meta-data. The most important fields here are the ‘lens’ and ‘categories’ variables. The ‘lens’ tells Singlet the name of the lens your scope is for. Singlet uses this to build DBus names and paths, and also to know where to install your scope. The ‘categories’ list will let you define a result item’s category using a descriptive name, rather than an integer.
model.append('http://loco.ubuntu.com/events/%s/%s/detail/' % (team['lp_name'], tevent['id']), team['mugshot_url'], self.lens.events, "text/html", tevent['name'], '%s\n%s' % (tevent['date_begin'], tevent['description']), '')
It’s important that the order of the categories in the Scope’s Meta matches the order of categories defined in the Lens you are targeting, since in the end it’s still just the position number that’s being passed back to the Dash.
After all this, I still had a little bit of time left in the day. And what good is supporting external scopes if you only have one anyway? So I spent 30 minutes creating another scope, one that will read from the Ubuntu Planet news feed:
The next step is to add some proper packaging to get these into the Ubuntu Software Center, but you impatient users can get them either from their respective bzr branches, or try the preliminary packages from the One Hundred Scopes PPA.
 Note that while lenses written for Singlet 0.1 will work in Singlet 0.2 on Precise, the reverse is not necessarily true. Singlet 0.2, as well as lenses and scopes written for it, will not work on Oneiric.