Back when I first started writing Unity lenses, I lamented the complexity required to get even the most basic Lens written and running. I wrote in that post about wanting to hide all of that unnecessary complexity. Well now I am happy to announce the first step towards that end: Singlet.
In optics, a “singlet” is a very simple lens. Likewise, the Singlet project aims to produce simple Unity lenses. Singlet targets opportunistic programmers who want to get search results into Unity with the least amount of work. By providing a handful of Python meta classes and base classes, Singlet lets you write a basic lens with a minimal amount of fuss. It hides all of the boilerplate code necessary to interface with GObject and DBus, leaving the developer free to focus solely on the purpose of their lens. With Singlet, the only thing a Lens author really needs to provide is a single search function.
Writing a Singlet
So what does a Singlet Lens look like? Here is a sample of the most basic lens, which produced the screenshot above:
#! /usr/bin/python from singlet.lens import SingleScopeLens, IconViewCategory, ListViewCategory from singlet.utils import run_lens class TestLens(SingleScopeLens): class Meta: name = 'test' cat1 = IconViewCategory("Cat One", "stock_yet") cat2 = ListViewCategory("Cat Two", "hint") def search(self, phrase, results): results.append('http://google.com/search?q=%s' % phrase, 'file', self.cat1, "text/html", phrase, phrase, '') results.append('http://google.com/search?q=%s' % phrase, 'file', self.cat2, "text/html", phrase, phrase, '') if __name__ == "__main__": import sys run_lens(TestLens, sys.argv)
As you can see, there isn’t much to it. SingleScopeLens is the first base class provided by Singlet. It creates an inner-scope for you, and connects it to the DBus events for handling a user’s search events. The three things you need to do, as a Lens author, is give it a name in the Meta class, define at least one Category, and most importantly implement your custom search(self, phrase, results) method.
Django developers will notice a similarity between Singlet Lenses and Django Models in their use of an inner Meta class. In fact, they work exactly the same way, though with different properties. At a minimum, you will need to provide a name for your lens. Everything else can either use default values, or will be extrapolated from the name. Everything in your Meta class, plus defaults and generated values, will be accessible in <your_class>._meta later on.
Again borrowing from Django Models, you add categories to your Singlet Lens by defining it in the Class’s scope itself, rather than in the __init__ method. One thing that I didn’t like about Categories when writing my previous lenses was that I couldn’t reference them when adding search results to the result model. Instead you have to give the numeric index of the category. In Singlet, the variable name you used when defining the category is converted to the numeric index for that category, so you easily reference it again when building your search results. But don’t worry, you category objects are still available to you in <your_class>._meta.categories if you want them.
The search is on
The core functionality of a Lens is the search. So it makes sense that the majority of your work should happen here. Singlet will call your search method, passing in the current search phrase and an empty results model. From there, it’s up to you to collect data from whatever source you are targeting, and start populating that results model.
You can handle the URI
Unity knows how to handle common URIs in your results, such as file:// and http:// uris. But often times your lens isn’t going to be dealing with results that map directly to a file or website. For those cases, you need to hook into DBus again to handle the URI of a selected result item, and return a specifically constructed GObject response. With Singlet, all you need to do is define a handle_uri method on your Lens, and it will take care of hooking it into DBus for you. Singlet also provides a couple of helper methods for your return value, either hide_dash_response to hide the dash after you’ve handled the URI, or update_dash_response if you want to leave it open.
Make it go
Once you’ve defined your lens, you need to be able to initialize it and run it, again using a combination of DBus and GObject. Singlet hides all of this behind the run_lens function in singlet.utils, which you should call at the bottom of your lens file as shown in the above snippet.
Lord of the files
There’s more to getting your Lens working that just the code, you also need to specify a .lens file describing your lens to Unity, and a .service file telling dbus about it. Singlet helps you out here too, by providing command line helpers for generating and installing these files. Suppose the code snippet above was in a file called testlens.py, once it’s written you can run “python testlens.py make” and it will write test.lens and unity-test-lens.service into your current directory. The data in these files comes from <your_lens>._meta, including the name, dbus information, description and icon. After running make you can run “sudo python testlens.py install”, this will copy your code and config files to where they need to be for Unity to pick them up and run them.
More to come
You can get the current Singlet code by branching lp:singlet. I will be working on getting it built and available via PyPi and a PPA in the near future, but for now just having it on your PYTHONPATH is enough to get started using it. Just be aware that if you make/install a Singlet lens, you need to make the Singlet package available on Unity’s PYTHONPATH as well or it won’t be able to run. I’ve already converted my Dictionary Lens to use Singlet, and will work on others while I grow the collection of Singlet base classes. If anybody has a common yet simple use case they would like me to target, please leave a description in the comments.