Create an Ubuntu Application Indicator in Python: step-by-step guide
When implementing the Vagrant AppIndicator for Ubuntu, I really lacked the documentation on this subject. I’ve found the official documentation too sparse and having broken links (there is still one broken by the time of this writing) and the only way to proceed was to experiment a lot and dig into open-source implementations of a couple of other application indicators. So, I’ve decided to release this guide, hopefully comprehensive enough and useful. Here I show, step-by-step, how the AppIndicator could be implemented in Python. I try to keep it short and concrete, yet understandable.
Minimal set-up - the very basic indicator
The very basic AppIndicator in Python would be like this:
Few notes on it:
-
First, we import Gtk and AppIndicator basic implementation. Thing to note here is that in some older documentation you may find importing
gtk
module - that would be an older Gtk 2, while we use Gtk+ 3 here. -
A most simple AppIndicator, in order to be actually displayed, would need to be activated (
set_status(...ACTIVE)
), and would need to have a menu associated with it. If any of these conditions is not met, the application will start, but there will be no AppIndicator displayed though. Thus, after instantiating the one, we set it up correctly.A note on AppIndicator constructor: it accepts a unique indicator name (so think of a name no one else would use), an icon name (more on it later, let’s put any string here for now) and an indicator category. You can
dir()
all categories fromappindicator.IndicatorCategory
to see which are available. The category typically impacts where in the system tray the AppIndicator is placed - that is, the ordering between AppIndicators. You may want to experiment with different categories to see what the result is. -
Lastly,
gtk.main()
starts the Gtk endless loop. This function call will not quit until the Gtk application ends itself. Thus, in the case of the code above - it never ends.
If you run this code now from Python REPL or, run this script
- you should now see the AppIndicator in the “system tray”, with a standard “no icon” icon.
This is our basic starting point. Let’s start improving it now.
Can’t stop?
First thing you’ll notice - there is no “normal” way to stop the AppIndicator. We didn’t add a call to stop the Gtk
main loop anywhere, and even Ctrl+C
doesn’t really work. For now, the only thing you can do is to find the id of the
process and kill it. Or, simply close the terminal you’ve started the application in.
First quick win would be to fix “Ctrl+C” behavior. Add:
and the following line into the main method, anywhere before the Gtk main loop is started:
This will make the AppIndicator reacting normally to the “Ctrl+C” in the terminal - at least it can be stopped now.
This sets the handler for “INT” signal processing - the one issued by the OS when “Ctrl+C” is typed. The handler we assign to it is the “default” handler, which, in case of the interrupt signal, is to stop execution.
However, for the end user we would certainly want to add a more convenient way to stop it - which would typically be a dedicated menu item in the indicator’s main menu. Let’s add it now.
Add menu items
AppIndicator is built on Gtk, so there is nothing specific in the way you create a menu. Gtk+ 3 has a great tutorial. Below is the example of the menu for out AppIndicator. Let’s add “Quit” menu item.
Add two new functions:
And, replace indicator.set_menu(gtk.Menu())
with indicator.set_menu(build_menu())
, to assign the new menu to an
indicator. Not digging into Gtk details, few notes on this however:
-
Do not forget to call
menu.show_all()
, or the menu will not contain the new items added to it. -
Action listener assigned to the menu item receives a single argument - an event source, which in this case is the menu item itself. We don’t need to use it in this implementation so could have replaced the argument name with
_
. -
Calling
gtk.main_quit()
stops the Gtk main loop, effectively making the previous call togtk.main()
to return, and thus, ourmain()
function to return and application to stop.
Now if you run it and click on an indicator icon - there will be “Quit” menu item, which, when clicked should stop the indicator application.
But, the indicator itself still looks quite ugly. Let’s find a better icon for it now.
Custom icon
There are two options for an indicator icon: use one from the icon library installed with the Gtk, or, use a custom one. To use the icon from the Gtk library, simply, give its name to the AppIndicator constructor. For example, following change:
Will result in:
Prebuilt Gtk icons depend on the Gtk distribution, but there are few which normally present in every distribution. You can search the documentation to find the list of all icons, like this one for example.
Although probably little better than “no icon” icon, yet this doesn’t look unique. Alternatively, you can use a custom
image as an icon. I think that the best choice for an icon is the SVG
image, as it will scale nicely if the UI theme
requires system tray icons to have size different from the one you’ve tested on.
Create your icon, or download one, or use this sample icon I’ve created for this guide. Save
it somewhere on disk, and use the path to an icon instead of the icon name when constructing the AppIndicator. Path should be absolute, so, if you save an icon into a file sample_icon.svg
in the same directory as the AppIndicator
python program, update the call to the constructor as follows:
Run, and now it should look like this:
Nice! Note that an icon can also be changed while the indicator is running, with indicator.set_icon()
method.
Showing balloon (bubble) notifications
With all above we have now a ready-to-use base for an AppIndicator. Now, the AppIndicator doesn’t do that much yet. As an example, let’s add some behavior to it - for example, give us some nerdy Chuck Norris joke from The Internet Chuck Norris Database. We would display jokes in a balloon notification typically popping out in upper right corner of the screen in Ubuntu.
We would need 3 elements here. First, let’s add a function to fetch a joke:
now, add a menu item and an action listener:
and, finally, show the joke in a balloon notification:
Again, few notes:
-
To show notifications we use notify API which we first import and initialize with the unique application name - in our case could be the same as the one used for an indicator itself.
-
To show a notification we construct a
Notification
instance andshow()
it. There is no 3rd argument in the code above, which is a path to an icon. If you passNone
as above, there will simply no icon. -
Be nice to Gtk and
uninit()
notify API before quitting the application
Now, if you run the AppIndicator at this point, clicking on its “Joke” menu should display a fun balloon notification:
That’s it! I take no more your attention, and hope this sample AppIndicator could serve a good base for a new indicator implementation ;)
You can find here the source code of this implementation and use it as you want (consider it as MIT). Or you can also check out the Vagrant AppIndicator for Ubuntu as a more real-world example which also provides theming support (dark & light UI themes) and a way to distribute an AppIndicator in a Python package.