|
| 1 | +"""Interface to the system tray icon and desktop notifications. |
| 2 | +
|
| 3 | +The SysTrayIcon class gives access to the "tk systray" command and |
| 4 | +the notify() function gives access to the "tk sysnotify" command. |
| 5 | +They require Tk 8.7/9.0 or newer. |
| 6 | +
|
| 7 | +Only one system tray icon is supported per Tcl interpreter. |
| 8 | +
|
| 9 | +On Windows, sending a notification requires that the system tray icon |
| 10 | +has been created first; the icon is also displayed in the |
| 11 | +notification. |
| 12 | +""" |
| 13 | + |
| 14 | +import tkinter |
| 15 | + |
| 16 | +__all__ = ["SysTrayIcon", "notify"] |
| 17 | + |
| 18 | + |
| 19 | +class SysTrayIcon: |
| 20 | + """The system tray icon. |
| 21 | +
|
| 22 | + Only one system tray icon is supported per Tcl interpreter. |
| 23 | + With exists false (the default) a new icon is created, and creating |
| 24 | + a second one raises TclError. With exists true this refers to the |
| 25 | + already-existing icon instead of creating one. |
| 26 | +
|
| 27 | + Supported configuration options are: |
| 28 | +
|
| 29 | + image: the image displayed in the system tray (required when |
| 30 | + creating an icon; it must be a photo image on Windows) |
| 31 | + text: the text displayed in the tooltip of the icon |
| 32 | + button1: a callback that is called when the icon is clicked |
| 33 | + with the left mouse button |
| 34 | + button3: a callback that is called when the icon is clicked |
| 35 | + with the right mouse button |
| 36 | + """ |
| 37 | + |
| 38 | + def __init__(self, master=None, *, exists=False, **options): |
| 39 | + if master is None: |
| 40 | + master = tkinter._get_default_root('use the system tray icon') |
| 41 | + self.master = master |
| 42 | + self._command_names = {} |
| 43 | + if exists: |
| 44 | + # Refer to the already-existing icon, reconfiguring it if |
| 45 | + # any options are given. |
| 46 | + if options: |
| 47 | + self._call('configure', options) |
| 48 | + else: |
| 49 | + if options.get('image') is None: |
| 50 | + raise TypeError( |
| 51 | + "the 'image' argument is required to create an icon") |
| 52 | + self._call('create', options) |
| 53 | + |
| 54 | + def _call(self, subcommand, cnf): |
| 55 | + # Call "tk systray" with the given options, registering and |
| 56 | + # unregistering the callback commands as needed. |
| 57 | + master = self.master |
| 58 | + cnf = dict(cnf) |
| 59 | + new_names = {} |
| 60 | + for key in ('button1', 'button3'): |
| 61 | + if key in cnf: |
| 62 | + command = cnf[key] |
| 63 | + if command is None: |
| 64 | + cnf[key] = '' |
| 65 | + elif callable(command): |
| 66 | + new_names[key] = cnf[key] = master._register(command) |
| 67 | + try: |
| 68 | + master.tk.call('tk', 'systray', subcommand, |
| 69 | + *master._options(cnf)) |
| 70 | + except tkinter.TclError: |
| 71 | + for name in new_names.values(): |
| 72 | + master.deletecommand(name) |
| 73 | + raise |
| 74 | + for key in ('button1', 'button3'): |
| 75 | + if key in cnf: |
| 76 | + old_name = self._command_names.pop(key, None) |
| 77 | + if old_name is not None: |
| 78 | + master.deletecommand(old_name) |
| 79 | + if key in new_names: |
| 80 | + self._command_names[key] = new_names[key] |
| 81 | + |
| 82 | + def configure(self, cnf=None, **kw): |
| 83 | + """Query or modify the options of the system tray icon. |
| 84 | +
|
| 85 | + With no arguments, return a dict of all option values. With a |
| 86 | + string argument, return the value of that option. Otherwise, |
| 87 | + set the given options. |
| 88 | + """ |
| 89 | + if kw: |
| 90 | + cnf = tkinter._cnfmerge((cnf, kw)) |
| 91 | + elif cnf: |
| 92 | + cnf = tkinter._cnfmerge(cnf) |
| 93 | + tk = self.master.tk |
| 94 | + if cnf is None: |
| 95 | + items = tk.splitlist(tk.call('tk', 'systray', 'configure')) |
| 96 | + return {items[i][1:]: items[i+1] for i in range(0, len(items), 2)} |
| 97 | + if isinstance(cnf, str): |
| 98 | + return tk.call('tk', 'systray', 'configure', '-' + cnf) |
| 99 | + self._call('configure', cnf) |
| 100 | + config = configure |
| 101 | + |
| 102 | + def cget(self, option): |
| 103 | + """Return the value of the given option of the system tray icon.""" |
| 104 | + return self.master.tk.call('tk', 'systray', 'configure', '-' + option) |
| 105 | + |
| 106 | + def exists(self): |
| 107 | + """Return whether the system tray icon exists.""" |
| 108 | + tk = self.master.tk |
| 109 | + return tk.getboolean(tk.call('tk', 'systray', 'exists')) |
| 110 | + |
| 111 | + def destroy(self): |
| 112 | + """Destroy the system tray icon.""" |
| 113 | + self.master.tk.call('tk', 'systray', 'destroy') |
| 114 | + for name in self._command_names.values(): |
| 115 | + self.master.deletecommand(name) |
| 116 | + self._command_names.clear() |
| 117 | + |
| 118 | + def notify(self, title, message): |
| 119 | + """Send a desktop notification with the given title and message.""" |
| 120 | + self.master.tk.call('tk', 'sysnotify', title, message) |
| 121 | + |
| 122 | + |
| 123 | +def notify(title, message, *, master=None): |
| 124 | + """Send a desktop notification with the given title and message. |
| 125 | +
|
| 126 | + On Windows, the system tray icon must have been created first. |
| 127 | + """ |
| 128 | + if master is None: |
| 129 | + master = tkinter._get_default_root('send a notification') |
| 130 | + master.tk.call('tk', 'sysnotify', title, message) |
0 commit comments