38

I'd just need a quick example on how to easily put an icon with python on my systray. This means: I run the program, no window shows up, just a tray icon (I've got a png file) shows up in the systray and when I right-click on it a menu appears with some options (and when I click on an option, a function is run). Is that possible? I don't need any window at all...

Examples / code snippets are REALLY appreciated! :D

Anirudh Ramanathan
  • 43,868
  • 20
  • 121
  • 177
Marco Moschettini
  • 1,258
  • 2
  • 11
  • 24
  • 2
    What OS? If linux, I'd look into say the source code of gmail-notify.py. See http://gmail-notify.sourceforge.net/download.php – dr jimbob Jun 17 '11 at 17:41
  • I guess it didn't really matter as fogglebird's wx code works fine in linux (specifically gnome system panel). – dr jimbob Jun 17 '11 at 18:33

9 Answers9

62

For Windows & Gnome

Here ya go! wxPython is the bomb. Adapted from the source of my Feed Notifier application.

import wx

TRAY_TOOLTIP = 'System Tray Demo'
TRAY_ICON = 'icon.png'


def create_menu_item(menu, label, func):
    item = wx.MenuItem(menu, -1, label)
    menu.Bind(wx.EVT_MENU, func, id=item.GetId())
    menu.AppendItem(item)
    return item


class TaskBarIcon(wx.TaskBarIcon):
    def __init__(self):
        super(TaskBarIcon, self).__init__()
        self.set_icon(TRAY_ICON)
        self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)

    def CreatePopupMenu(self):
        menu = wx.Menu()
        create_menu_item(menu, 'Say Hello', self.on_hello)
        menu.AppendSeparator()
        create_menu_item(menu, 'Exit', self.on_exit)
        return menu

    def set_icon(self, path):
        icon = wx.IconFromBitmap(wx.Bitmap(path))
        self.SetIcon(icon, TRAY_TOOLTIP)

    def on_left_down(self, event):
        print 'Tray icon was left-clicked.'

    def on_hello(self, event):
        print 'Hello, world!'

    def on_exit(self, event):
        wx.CallAfter(self.Destroy)


def main():
    app = wx.PySimpleApp()
    TaskBarIcon()
    app.MainLoop()


if __name__ == '__main__':
    main()
Jonathan
  • 5,179
  • 4
  • 41
  • 59
FogleBird
  • 65,535
  • 24
  • 118
  • 129
  • 1
    No problem. Use `item.SetBitmap(wx.Bitmap(path))` in `create_menu_item` if you want icons on your menu items too. (Just to make it look nice.) – FogleBird Jun 17 '11 at 17:59
  • 3
    I'd also look into using PyEmbeddedImage: http://www.wxpython.org/docs/api/wx.lib.embeddedimage.PyEmbeddedImage-class.html – K. Brafford Jun 17 '11 at 18:12
  • 2
    The icon doesn't appear on the Ubuntu 12.10 Unity tray.. Got no error, just can't see it.. – James T Snell Jun 05 '13 at 02:35
  • 1
    Unity uses appindicator instead of the freedesktop.org System Tray Protocol which wxWidgets requires. http://docs.wxwidgets.org/trunk/classwx_task_bar_icon.html – mr.freeze Dec 31 '13 at 23:13
  • Hey I'm getting the following output : "Segmentation fault 11" Any idea how to fix this ? I'm running a Mac OS X – Shonu93 Feb 23 '14 at 14:08
  • I made an ubuntu post as well, below – Jonathan Aug 04 '15 at 22:02
  • 1
    I got AttributeError: 'module' object has no attribute 'TaskBarIcon'. My python version is 2.7 – SKG Apr 15 '16 at 11:38
  • wxPython is a huge, heavy library. I am not sure this response can validly address the part of the question that says "... to *easily* put an icon with python on my systray." – WhyWhat Jul 23 '20 at 07:06
15

wx.PySimpleApp deprecated, here's how to use wx.App instead

Took me while to figure this out so I thought I'd share. wx.PySimpleApp is deprecated in wxPython 2.9 and beyond. Here's FogleBird's original script using wx.App instead.

import wx

TRAY_TOOLTIP = 'System Tray Demo'
TRAY_ICON = 'icon.png'

def create_menu_item(menu, label, func):
    item = wx.MenuItem(menu, -1, label)
    menu.Bind(wx.EVT_MENU, func, id=item.GetId())
    menu.AppendItem(item)
    return item

class TaskBarIcon(wx.TaskBarIcon):
    def __init__(self, frame):
        self.frame = frame
        super(TaskBarIcon, self).__init__()
        self.set_icon(TRAY_ICON)
        self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)

    def CreatePopupMenu(self):
        menu = wx.Menu()
        create_menu_item(menu, 'Say Hello', self.on_hello)
        menu.AppendSeparator()
        create_menu_item(menu, 'Exit', self.on_exit)
        return menu

    def set_icon(self, path):
        icon = wx.IconFromBitmap(wx.Bitmap(path))
        self.SetIcon(icon, TRAY_TOOLTIP)

    def on_left_down(self, event):
        print 'Tray icon was left-clicked.'

    def on_hello(self, event):
        print 'Hello, world!'

    def on_exit(self, event):
        wx.CallAfter(self.Destroy)
        self.frame.Close()

class App(wx.App):
    def OnInit(self):
        frame=wx.Frame(None)
        self.SetTopWindow(frame)
        TaskBarIcon(frame)
        return True

def main():
    app = App(False)
    app.MainLoop()


if __name__ == '__main__':
    main()
dlk
  • 159
  • 1
  • 2
  • 3
    Running this code returns: AttributeError: 'module' object has no attribute 'TaskBarIcon' – Pitto Aug 11 '17 at 08:15
  • 2
    @Pitto Same here, but I discovered that some classes and constants have been moved to a subpackage called wx.adv so instead of `wx.TaskBarIcon` you should use `wx.adv.TaskBarIcon`. Same for `EVT_TASKBAR_LEFT_DOWN` – Clayton A. Alves Jan 19 '18 at 18:54
  • This solution works on Gnome 3.26 (Ubuntu 18.04) and + if the extension TopIcon is enabled. The system tray is gone in modern Gnome and application indicators replace them. Anybody knows how to create application indicators with WX? This [link](https://askubuntu.com/questions/833760/how-to-add-a-script-as-an-icon-in-topbar) shows how to do one, without WX though. – Hans Deragon Mar 31 '19 at 11:54
13

2018 version

import wx.adv
import wx
TRAY_TOOLTIP = 'Name' 
TRAY_ICON = 'icon.png' 


def create_menu_item(menu, label, func):
    item = wx.MenuItem(menu, -1, label)
    menu.Bind(wx.EVT_MENU, func, id=item.GetId())
    menu.Append(item)
    return item


class TaskBarIcon(wx.adv.TaskBarIcon):
    def __init__(self, frame):
        self.frame = frame
        super(TaskBarIcon, self).__init__()
        self.set_icon(TRAY_ICON)
        self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)

    def CreatePopupMenu(self):
        menu = wx.Menu()
        create_menu_item(menu, 'Site', self.on_hello)
        menu.AppendSeparator()
        create_menu_item(menu, 'Exit', self.on_exit)
        return menu

    def set_icon(self, path):
        icon = wx.Icon(path)
        self.SetIcon(icon, TRAY_TOOLTIP)

    def on_left_down(self, event):      
        print ('Tray icon was left-clicked.')

    def on_hello(self, event):
        print ('Hello, world!')

    def on_exit(self, event):
        wx.CallAfter(self.Destroy)
        self.frame.Close()

class App(wx.App):
    def OnInit(self):
        frame=wx.Frame(None)
        self.SetTopWindow(frame)
        TaskBarIcon(frame)
        return True

def main():
    app = App(False)
    app.MainLoop()


if __name__ == '__main__':
    main()
elig
  • 1,383
  • 8
  • 22
  • Why is the frame needed? Why self.Destroy() instead of self.Close() as in the wxPython basic tutorial? – Xaser Nov 06 '18 at 09:26
6

If you can guarantee windows and you do not want to introduce the heavy dependencies of wx, you can do this with the pywin32 extensions.

Also see this question.

Community
  • 1
  • 1
Mark
  • 101,574
  • 16
  • 158
  • 219
  • 1
    @kbec, thanks for the heads up. I updated my answer with some newer links. It also looks like I duplicated an older answer, so I'm included to delete mine, but I'll leave it here for Google's sake. – Mark Feb 26 '13 at 15:00
5

For Ubuntu

class TrayIcon:
    def init():


iconPath = {"Windows":os.path.expandvars("%PROGRAMFILES%/MyProgram/icon.png"),
                  "Linux":"/usr/share/icons/myprogramicon.png"}        
    if platform.system()=="Linux":
        import gtk
        import appindicator # Ubuntu apt-get install python-appindicator 

    # Create an application indicator
    try:
        gtk.gdk.threads_init()
        gtk.threads_enter()
        icon = iconPath[platform.system()]
        indicator = appindicator.Indicator("example-simple-client", "indicator-messages", appindicator.CATEGORY_APPLICATION_STATUS)
        indicator.set_icon(icon)
        indicator.set_status (appindicator.STATUS_ACTIVE)
        indicator.set_attention_icon ("indicator-messages-new")
        menu = gtk.Menu()

        menuTitle = "Quit"   
        menu_items = gtk.MenuItem(menuTitle)
        menu.append(menu_items)
        menu_items.connect("activate", TrayIcon.QuitApp, menuTitle)
        menu_items.show()

        menuTitle = "About My Program"
        menu_items = gtk.MenuItem(menuTitle)
        menu.append(menu_items)
        menu_items.connect("activate", TrayIcon.AboutApp, menuTitle)
        menu_items.show()   

        indicator.set_menu(menu)    
    except:
        pass

    # Run the app indicator on the main thread.
    try:

        t = threading.Thread(target=gtk.main)
        t.daemon = True # this means it'll die when the program dies.
        t.start()
        #gtk.main()

    except:
        pass
    finally:
        gtk.threads_leave()     

@staticmethod
def AboutApp(a1,a2):
    gtk.threads_enter()
    dialog = gtk.Dialog("About",
                        None,
                        gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                        (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
    label = gtk.Label("My Program v0.0.1, (C)opyright ME 2015. All rights reserved.")
    dialog.vbox.pack_start(label)
    label.show()
    label2 = gtk.Label("example.com\n\nFor more support contact me@gmail.com")
    label2.show()
    dialog.action_area.pack_end(label2)
    response = dialog.run()
    dialog.destroy()
    gtk.threads_leave()

@staticmethod
def QuitApp(a1, a2):
    sys.exit(0)

Cross-Platform

See PyQt: Show menu in a system tray application

Community
  • 1
  • 1
Jonathan
  • 5,179
  • 4
  • 41
  • 59
2

An alternative if you are trying to run a python based program in the background you can run it as a service. Check out this active state recipe its pretty useful. I believe one of the options is to convert your application to exe with py2exe or pyinstall.

http://code.activestate.com/recipes/551780/

jkdba
  • 1,874
  • 3
  • 17
  • 29
  • is it possible to have a GUI run as a service? – Isquare1 Jul 31 '20 at 09:58
  • @Isquare1 my python knowledge is a bit dated but [this](https://stackoverflow.com/questions/3343793/starting-a-gui-process-from-a-python-windows-service) SO question seems to answer it. – jkdba Jul 31 '20 at 17:11
2

Yes. There is a cross-platform example on wiki.wxpython.org that I've tested with python 2.7 (minconda install) on macOS High Sierra (10.13.3), Windows 7, and gnome 3/centos7. It is here (ignore the page title): https://wiki.wxpython.org/Custom%20Mac%20OsX%20Dock%20Bar%20Icon

Small mods are needed for python 3.6:

  • you must import wx.adv
  • wx.TaskBarIcon becomes wx.adv.TaskBarIcon
  • wx.IconFromBitmap becomes wx.Icon

Gnome 3 required installation of TopIcons Plus.

Since you don't want to have the window display (" no window shows up, just a tray icon"), simply comment out the following line (though you still want to keep the wx.Frame parent):

frame.Show(True)

And since you want to use your own .png icon, remove the WXPdemo image and embeddedimage stuff and replace

icon = self.MakeIcon(WXPdemo.GetImage())

with, for example

icon = wx.Icon('icon.png')

In my experience, this will provide a good start for adapting or extending further.

Generic Ratzlaugh
  • 609
  • 1
  • 4
  • 13
1

For an example, refer to this thread -> wx question.

wxPython "classic" -> [new API] wxPython 'Phoenix' (Py3)

8-Bit Borges
  • 8,099
  • 15
  • 64
  • 132
viteck
  • 11
  • 1
1

Three is a package called pystray(bad name, just say it out loud) but works like a charm and is more lightweight than wx ot qt. These are the links
https://pystray.readthedocs.io/en/latest/index.html
https://pypi.org/project/pystray/