5

I'm writing a small Python 3 app which uses AppIndicator3 to place an icon in the top bar, and then change that icon in response to user actions. Simple, right? Because the app is small, it needs to run from its source directory without any kind of installation.

The problem is that AppIndicator3.set_icon() requires a str with an icon name, not a path to said icon.

How can I persuade AppIndicator3 to let me give it either a filename or a Pixbuf? Alternatively, how can I add my icon directory to the icon name search path? (I tried AppIndicator3.set_icon_theme_path(), but my icon names are still unrecognized.

  • Setting an icon from path can be done very well, but even then you cannot simply update the icon from the same thread as the one running the interface. The question needs to be "How can I update an icon of an indicator while it is running". – Jacob Vlijm May 10 '16 at 08:02
  • Actually, I can update the icon just fine if I use an icon from another package. I'm currently using icons from caffeine-plus. But I want to remove an unnecessary dependency and I don't want to need an installer. – Scott Severance May 10 '16 at 13:59
  • Anyway, if you can help me set an AppIndicator3 icon from a filename, that'd be great. By the way, I'm using a bit of your code for some of my app's core functionality. Thanks! – Scott Severance May 10 '16 at 14:12
  • Sure! I will post late tonight or tomorrow, have to run... – Jacob Vlijm May 10 '16 at 17:54
  • Hi Scott, posted. Please let me know if it is what you are looking for. – Jacob Vlijm May 10 '16 at 21:13

1 Answers1

6

To use a path to the icon is best illustrated with an example. In the example below, I keep the icons in the same directory as the script (indicator), which seems a convenient solution in your case.

The bottom line is that once you initiated your indicator:

class Indicator():
    def __init__(self):
        self.app = "<indicator_name>"
        iconpath = "/path/to/initial/icon/"

        -------------------------------

        self.testindicator = AppIndicator3.Indicator.new(
        self.app, iconpath,
        AppIndicator3.IndicatorCategory.OTHER)

        -------------------------------

you can change the icon with:

        self.testindicator.set_icon("/path/to/new/icon/")

enter image description here enter image description here

Example

In the example below, all icons, nocolor.png, purple.png and green.png are stored together with the script, but the path to the icons, set in

currpath = os.path.dirname(os.path.realpath(__file__))

could be anywhere.

#!/usr/bin/env python3
import os
import signal
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import Gtk, AppIndicator3

currpath = os.path.dirname(os.path.realpath(__file__))

class Indicator():
    def __init__(self):
        self.app = 'show_proc'
        iconpath = currpath+"/nocolor.png"
        # after you defined the initial indicator, you can alter the icon!
        self.testindicator = AppIndicator3.Indicator.new(
            self.app, iconpath,
            AppIndicator3.IndicatorCategory.OTHER)
        self.testindicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)       
        self.testindicator.set_menu(self.create_menu())

    def create_menu(self):
        menu = Gtk.Menu()
        item_quit = Gtk.MenuItem(label='Quit')
        item_quit.connect('activate', self.stop)
        item_green = Gtk.MenuItem(label='Green')
        item_green.connect('activate', self.green)
        item_purple = Gtk.MenuItem(label='Purple')
        item_purple.connect('activate', self.purple)
        menu.append(item_quit)
        menu.append(item_green)
        menu.append(item_purple)
        menu.show_all()
        return menu

    def stop(self, source):
        Gtk.main_quit()

    def green(self, source):
        self.testindicator.set_icon(currpath+"/green.png")

    def purple(self, source):
        self.testindicator.set_icon(currpath+"/purple.png")

Indicator()
signal.signal(signal.SIGINT, signal.SIG_DFL)
Gtk.main()

Note

...that if you need to update the icon from a second thread, you need to use

GObject.threads_init()

before

Gtk.main()

and you need to update the interface (either icon or indicator text) by using:

GObject.idle_add()

as applied e.g. here and here.

Jacob Vlijm
  • 83,767
  • Thanks. I had tried that before and it didn't work. It turns out that I had changed the names of the icon files and forgotten to update the code. So, I was left trying all sorts of other things. You set me back on the right track. – Scott Severance May 10 '16 at 23:23
  • @AntonYablokov Sorry I rejected your edit a while ago. On 19.04, indeed got the deprecated warning on not using argument label.. – Jacob Vlijm Feb 19 '19 at 13:11