Create Indicator Applet for Ubuntu Unity (with Python!)

We’re going to walk through the process of creating a status indicator applet for the latest version of Ubuntu running Unity. In this example we’re going to create a basic new mail indicator which works with GMail. This will give you a solid grounding in the basic structure of an applet while providing a real-world (albeit simplistic) example that you can easily extend.

Let’s go through the final script piece by piece, first our header:

#!/usr/bin/env python

import sys
import gtk
import appindicator

import imaplib
import re

PING_FREQUENCY = 60

The first line instructs the system to find the current environment’s version of python and use it to run this file. The first three imports are probably going to be used in every indicator you use. sys is used so we can offer a “Quit” option, GTK drives the interface, and appindicator provides Unity-specific code for creating indicator applets. The last two includes are required for this indicator’s specific functionality, checking GMail. Finally we have one application wide constant which dictates how often we should ping (or check) GMail’s servers for new mail, in seconds.

class CheckGMail:
    def __init__(self):
        self.ind = appindicator.Indicator("debian-doc-menu",
                                           "indicator-messages",
                                           appindicator.CATEGORY_APPLICATION_STATUS)
        self.ind.set_status(appindicator.STATUS_ACTIVE)
        self.ind.set_attention_icon("new-messages-red")

        self.menu_setup()
        self.ind.set_menu(self.menu)

Next we create a class called CheckGMail which will hold all of our indicator’s functionality. To initialize our class first we create the variable “ind” which holds an Indicator object. According to the API this constructor takes 3 (or 4) arguments, a unique id for our indicator, an icon name, and an indicator category.

We set an initial status for our indicator with ind.set_status and then define the icon to use when our indicator requires attention (in this case when we have new mail waiting). Then we call our custom menu_setup() function (which we haven’t defined yet), and then the set_menu() function on the Indicator object which takes a GTK menu as an argument and attaches it to our indicator.

def menu_setup(self):
        self.menu = gtk.Menu()

        self.quit_item = gtk.MenuItem("Quit")
        self.quit_item.connect("activate", self.quit)
        self.quit_item.show()
        self.menu.append(self.quit_item)

Here is the aforementioned menu_setup() function. Here we create a new GTK Menu and then define a single menu item called “quit_item”. We define the text label for the menu, and then connect it with an action. We instruct the menu item to launch the quit() function when the item is “activated” (clicked) and then add the item to our menu variable.

def main(self):
        self.check_mail()
        gtk.timeout_add(PING_FREQUENCY * 1000, self.check_mail)
        gtk.main()

The main() function constitutes our primary program loop. When our indicator is first loaded we run this function. First it runs the check_mail() function (which has yet to be defined), and then creates a timeout. This causes our program to wait the specified amount of time and then run the given callback function, which is in this case, check_mail(). Then finally we run our main() function again, putting our program into an infinite loop, since we’d like our applet to continually check mail as long as it’s running.

def quit(self, widget):
        sys.exit(0)

Here we define the quit() function which we referenced in menu_setup(). This function makes a call to sys.exit which will cause our application to stop running. At this point the process will stop and our indicator applet will disappear.

def check_mail(self):
        messages, unread = self.gmail_checker(‘myaddress@gmail.com’,‘mypassword’)
        if unread > 0:
            self.ind.set_status(appindicator.STATUS_ATTENTION)
        else:
            self.ind.set_status(appindicator.STATUS_ACTIVE)
        return True

This is the core functionality of this specific indicator applet. We’re calling the function gmail_checker() which we’ve borrowed from Martin Brook for the purposes of this example. We load in the number of total messages and the number of unread messages from the provided GMail account. (I should note, this will work with Google Apps account, you are not required to have a @gmail.com address). If there is more than one unread messages found, we change the status of our indicator to STATUS_ATTENTION, which is this case turns our icon red, notifying you of new messages.

def gmail_checker(self, username, password):
        i = imaplib.IMAP4_SSL(‘imap.gmail.com’)
        try:
            i.login(username, password)
            x, y = i.status(‘INBOX’, ‘(MESSAGES UNSEEN)’)
            messages = int(re.search(‘MESSAGES\s+(\d+)’, y[0]).group(1))
            unseen = int(re.search(‘UNSEEN\s+(\d+)’, y[0]).group(1))
            return (messages, unseen)
        except:
            return False, 0

This is the final function of our indicator applet class, and it is the code that we borrowed from Martin. It creates an SSL encrypted connection to an IMAP server, in this case GMail, and will attempt to determine how many total and unread messages are available.

if __name__ == "__main__":
    indicator = CheckGMail()
    indicator.main()

We come a the close of our file with some basic python initialization. This special block will be run when our file is called directly. It instantiated an object of the CheckGMail class and then runs the main() function, which set’s everything off for us.

Here is our entire file:

#!/usr/bin/env python

import sys
import gtk
import appindicator

import imaplib
import re

PING_FREQUENCY = 10 # seconds

class CheckGMail:
    def __init__(self):
        self.ind = appindicator.Indicator("new-gmail-indicator",
                                           "indicator-messages",
                                           appindicator.CATEGORY_APPLICATION_STATUS)
        self.ind.set_status(appindicator.STATUS_ACTIVE)
        self.ind.set_attention_icon("new-messages-red")

        self.menu_setup()
        self.ind.set_menu(self.menu)

    def menu_setup(self):
        self.menu = gtk.Menu()

        self.quit_item = gtk.MenuItem("Quit")
        self.quit_item.connect("activate", self.quit)
        self.quit_item.show()
        self.menu.append(self.quit_item)

    def main(self):
        self.check_mail()
        gtk.timeout_add(PING_FREQUENCY * 1000, self.check_mail)
        gtk.main()

    def quit(self, widget):
        sys.exit(0)

    def check_mail(self):
        messages, unread = self.gmail_checker(‘myaddress@gmail.com’,‘mypassword’)
        if unread > 0:
            self.ind.set_status(appindicator.STATUS_ATTENTION)
        else:
            self.ind.set_status(appindicator.STATUS_ACTIVE)
        return True

    def gmail_checker(self, username, password):
        i = imaplib.IMAP4_SSL(‘imap.gmail.com’)
        try:
            i.login(username, password)
            x, y = i.status(‘INBOX’, ‘(MESSAGES UNSEEN)’)
            messages = int(re.search(‘MESSAGES\s+(\d+)’, y[0]).group(1))
            unseen = int(re.search(‘UNSEEN\s+(\d+)’, y[0]).group(1))
            return (messages, unseen)
        except:
            return False, 0

if __name__ == "__main__":
    indicator = CheckGMail()
    indicator.main()

Now we just need to mark the file as executable and run it to see the results.

chmod +x check_gmail.py
./check_gmail.py

Now if you have a bunch of unread messages in your gmail account it will always be red, so you’ll have to extend this example if you want to make the new message checking more intelligent. We’ve learned how to setup a basic indicator applet with it’s own menu and icon, and give it a useful piece of functionality. You could easily replace the check_email() function with almost anything else you could program in python normally. Hope this helps to get you started with creating indicators, if you have any questions or comments, please let me know!

  • vamsi

    how to specify the location of a custom icon as the application icon

  • dread

    Thanks for the article! It’s very useful!

  • Ekoz

    Thanks to this example I’m creating my own indicator :) So thank you.

    But I’m wondering about how make a .deb or an executable to give it to friends, or any people…
    I mean, run it on startup, and hide sources…

    • Mara

      python scripts are executable, however if you want to package it, you can do so using many google-able options. Honestly I’d just say throw a copy of python2.6, and any of the options that were set to include, and set those files within a folder, then add this file but changing the #!/usr/bin/env python to #!./python no reason to really push this script through the env command first anyways and you’ll want to package with a copy of what’s available.

      Then you can work on distributing it with perhaps an upper level directory to those files set with a setup or launch script for it. If you wanted to make it into a startup service, just make the “setup” script copy that folder into something like /usr/sbin/myscripts/thisthing then build a simple script to execute it and drop it into /etc/init.d/ since this was build one a ubuntu principle, you should be able to have some nice point and click ability to set it as a startup script or you can again google and find how to make startup scripts.

      most .deb files will just throw the scripts required into a folder and execute them elsewhere anyways. Most of the time you can find the sources for many linux things especially python scripts. Python itself does not actually have a compiler, as it more or less is it’s own compiler. If you’d want, you can make a C script that will run this code through a python interpreter on the system however that’s entirely irrelevant and 90% of the time, no one in the linux world wants to do that shit. :)

  • Pingback: LAMP індикатор для панелі Unity « Франківські нотатки

  • hansvi

    So if I understand it correctly, the main loop checks mail, waits, checks mail twice in a row, waits, checks mail twice etc. (first call from the timeout, second call from the tail-recursive call to main).

  • KamronBennett

    This is a brilliant piece, this exactly what I have been looking for for months. I learn better by doing stuff like this. Too bad there aren’t more like this that gives you a clear walkthrough of how its done. This way you can learn stuff applicable to other things – thanks man!

    • http://conjurecode.com/ David

      You’re very welcome! I too get frustrated with incomplete walkthroughs or tutorials that assume you know a lot, so I try to keep a beginner’s frame of mind when I write so that new concepts aren’t glossed over. I typically write these shortly after I’ve learned something myself, so it is easier to accomplish this.

  • Jason

    Great! Thanks a lot for your paper. I will create my own indicators.

  • Benjamin

    Hi, do you know if I can make this work under Pantheon on eOS Luna? I’ve tried and it returns: “(checkgmail.py:11398): Gtk-WARNING **: Unable to locate theme engine in module_path: “pixmap” “

    • startouf

      I m Luna user too and Luna is not using Python (I think???!) for its panel.. To see further.. I m guarding this article in my favourites, really well done, thanks for newbies ! :)

  • guest

    What if I don’t want to write my password in plaintext?