Singlet, part 0.2

Posted on January 30, 2012 by Michael Hall

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
class CommunityLens(Lens): 

    class Meta:
        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:


class LocoTeamsScope(Scope): 

    class Meta:
        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
        if os.path.exists(os.path.expanduser('~/.bazaar/bazaar.conf')):
            try:
                import configparser
            except ImportError:
                import ConfigParser as configparser
            bzrconf = configparser.ConfigParser()
            bzrconf.read(os.path.expanduser('~/.bazaar/bazaar.conf'))
            try:
                self.lpusername = bzrconf.get('DEFAULT', 'launchpad_username')
            except configparser.NoOptionError:
                pass 

    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('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.

spacer

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:

spacer

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.

 

This entry was posted in LoCo, OpenSource, Programming, Work and tagged canonical, community, experiment, locodir, python, singlet, ubuntu, unity, work. Bookmark the permalink.

13 Responses to Singlet, part 0.2

  1. spacer Randall says:
    January 30, 2012 at 10:53 am

    Great work Michael. Thank you!

    Reply
  2. Pingback: Looking for Quickly template help | Michael Hall's Blog

  3. Pingback: Learn Ubuntu Development in just 3 days! | Michael Hall's Blog

  4. spacer Tory says:
    February 6, 2012 at 5:10 am

    I am trying to make a websearch lens and google scope with the latest singlet, I’m using Ubuntu 11.10, is Unity 5 required for this version? Or does the latest version still support Unity 4 as well?

    I’d hate to have to use singlet 1 and have to make it as a SingleScopeLens until Precise is finished…

    However if it does support Unity 4, the error I’m getting when I use ‘setsid unity’ is this:
    WARN 2012-02-06 02:06:14 unity.glib.dbusproxy GLibDBusProxy.cpp:269 Calling method failed: GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such interface `com.canonical.Unity.Lens’ on object at path /unity/singlet/lens/web_search

    And if I use ‘sudo python web_search’ I get this:
    (process:2504): libunity-DEBUG: unity-scope-factory.vala:57: Searching for Scopes in /usr/share/unity/lenses/web_search
    (process:2504): libunity-DEBUG: unity-scope-factory.vala:102: Successfully loaded /usr/share/unity/lenses/web_search/google.scope: unity.singlet.lens.web_search.google | /unity/singlet/lens/web_search/google
    (process:2504): libunity-DEBUG: unity-scope-proxy-remote.vala:115: Scope vanished: unity.singlet.lens.web_search.google
    (process:2504): libunity-DEBUG: unity-scope-proxy-remote.vala:105: Scope appeared: unity.singlet.lens.web_search.google

    (process:2504): libunity-WARNING **: unity-scope-proxy-remote.vala:97: Unable to connect to Scope (/unity/singlet/lens/web_search/google @ unity.singlet.lens.web_search.google): GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such interface `com.canonical.Unity.Scope’ on object at path /unity/singlet/lens/web_search/google

    (process:2504): libunity-WARNING **: unity-scope-proxy-remote.vala:97: Unable to connect to Scope (/unity/singlet/lens/web_search/google @ unity.singlet.lens.web_search.google): GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such interface `com.canonical.Unity.Scope’ on object at path /unity/singlet/lens/web_search/google
    (process:2504): libunity-DEBUG: unity-scope-proxy-remote.vala:115: Scope vanished: unity.singlet.lens.web_search.google
    The DBus info is all correct in the .service, .lens, and .scope files

    Reply
    • spacer Michael Hall says:
      February 6, 2012 at 8:07 am

      The latest Singlet only works on Unity 5 (Ubuntu 12.04)

      Reply
  5. spacer Tory says:
    February 6, 2012 at 11:02 am

    OK, thanks, I guess I’ll write a SingleScopeLens for now then…

    Reply
  6. spacer Tory says:
    February 6, 2012 at 6:50 pm

    OK, I got my scope working using singlet 0.1, well sort of, but the search results are not showing up, also, I installed your test lens from singlet 0.1, and it has the same problem, it acts like it works, but no results show up, all lenses I installed via softwtare center are working fine. When you type in the search the circle is spinning as if it’s searching, then it stops spinning, but nothing shows.

    I should also note that when I run setsid unity in a terminal, and then try to use the singlets, there are no error messages, the singlets are displaying the same info as all the working lenses.
    I’m running Ubuntu 11.10 with Unity 4.28.0

    On another note, I realized that at least with singlet 0.1, you can not use any special characters in the name, I was using web-search and the ‘-’ was causing it to fail, as would ‘_’, I had to use websearch as the name, don’t know why it does this, but it does.

    Reply
    • spacer Michael Hall says:
      February 6, 2012 at 9:17 pm

      Can you run the lens script from the command line, and see if you get any errors? I’ve seen this happen when the search code itself throws an error.

      Reply
  7. spacer Tory says:
    February 6, 2012 at 11:51 pm

    It doesn’t throw any errors, it just says it’s searching for scopes, also, I used my searching data in a test python program and it gets all the data correctly.

    Reply
    • spacer Michael Hall says:
      February 7, 2012 at 8:57 am

      Is your source code available somewhere I could look at it?

      Reply
  8. spacer Tory says:
    February 6, 2012 at 11:55 pm

    Also, like I wrote earlier the test lens included in singlet does the same thing.

    Reply
  9. spacer Tory says:
    February 7, 2012 at 3:11 pm

    I’ll post it here:

    import sys, urllib, json
    from singlet.lens import SingleScopeLens, ListViewCategory
    from singlet.utils import run_lens

    class WebSearchLens(SingleScopeLens):
    class Meta:
    name = “websearch”
    description = “Search the web”
    search_hint = “Search Google and Wikipedia”
    icon = “websearch.svg”
    search_on_blank = True

    webcrawler = ListViewCategory(‘Web Crawler’, ‘webcrawler.svg’)
    wiki = ListViewCategory(‘Wiki’, ‘file’)

    def search(self, search, model):

    if len(search) == 0:
    model.append(‘google.com/',
    “/usr/share/unity/lenses/websearch/webcrawler.svg”,
    self.webcrawler,
    “text/html”,
    ‘Google Home Page’)
    else:
    ##format search string into url#
    query = urllib.urlencode({‘q’: search})
    url = “ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=large&%s” % query

    ##open and read the url, convert to Python object#
    search_results = urllib.urlopen(url).read()
    data = json.loads(search_results)['responseData']

    ##retrieve desired info from data#
    for item in data['results']:
    title = item['titleNoFormatting']
    url = item['url']
    description = item['content']
    model.append(url,
    “/usr/share/unity/lenses/websearch/webcrawler.svg”,
    self.webcrawler,
    “text/html”,
    title, description)

    if __name__ == “__main__”:
    run_lens(WebSearchLens, sys.argv)

    Reply
  10. spacer Tory says:
    February 7, 2012 at 3:17 pm

    And don’t worry, my indentation was all correct, the comment just removed it spacer

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

*

*

You may use these HTML tags and attributes: <a class="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>