1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# KDE, App-Indicator or Qt Systray 5# Copyright (C) 2011-2018 Filipe Coelho <falktx@falktx.com> 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2 of the License, or 10# any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# For a full copy of the GNU General Public License see the COPYING file 18 19# Imports (Global) 20import os, sys 21 22if True: 23 from PyQt5.QtCore import QTimer 24 from PyQt5.QtGui import QIcon 25 from PyQt5.QtWidgets import QAction, QMainWindow, QMenu, QSystemTrayIcon 26else: 27 from PyQt4.QtCore import QTimer 28 from PyQt4.QtGui import QIcon 29 from PyQt4.QtGui import QAction, QMainWindow, QMenu, QSystemTrayIcon 30 31try: 32 if False and os.getenv("DESKTOP_SESSION") in ("ubuntu", "ubuntu-2d") and not os.path.exists("/var/cadence/no_app_indicators"): 33 from gi import require_version 34 require_version('Gtk', '3.0') 35 from gi.repository import Gtk 36 require_version('AppIndicator3', '0.1') 37 from gi.repository import AppIndicator3 as AppIndicator 38 TrayEngine = "AppIndicator" 39 40 #elif os.getenv("KDE_SESSION_VERSION") >= 5: 41 #TrayEngine = "Qt" 42 43 #elif os.getenv("KDE_FULL_SESSION") or os.getenv("DESKTOP_SESSION") == "kde-plasma": 44 #from PyKDE5.kdeui import KAction, KIcon, KMenu, KStatusNotifierItem 45 #TrayEngine = "KDE" 46 47 else: 48 TrayEngine = "Qt" 49except: 50 TrayEngine = "Qt" 51 52print("Using Tray Engine '%s'" % TrayEngine) 53 54iActNameId = 0 55iActWidget = 1 56iActParentMenuId = 2 57iActFunc = 3 58 59iSepNameId = 0 60iSepWidget = 1 61iSepParentMenuId = 2 62 63iMenuNameId = 0 64iMenuWidget = 1 65iMenuParentMenuId = 2 66 67# Get Icon from user theme, using our own as backup (Oxygen) 68def getIcon(icon, size=16): 69 return QIcon.fromTheme(icon, QIcon(":/%ix%i/%s.png" % (size, size, icon))) 70 71# Global Systray class 72class GlobalSysTray(object): 73 def __init__(self, parent, name, icon): 74 object.__init__(self) 75 76 self._app = None 77 self._parent = parent 78 self._gtk_running = False 79 self._quit_added = False 80 81 self.act_indexes = [] 82 self.sep_indexes = [] 83 self.menu_indexes = [] 84 85 if TrayEngine == "KDE": 86 self.menu = KMenu(parent) 87 self.menu.setTitle(name) 88 self.tray = KStatusNotifierItem() 89 self.tray.setAssociatedWidget(parent) 90 self.tray.setCategory(KStatusNotifierItem.ApplicationStatus) 91 self.tray.setContextMenu(self.menu) 92 self.tray.setIconByPixmap(getIcon(icon)) 93 self.tray.setTitle(name) 94 self.tray.setToolTipTitle(" ") 95 self.tray.setToolTipIconByPixmap(getIcon(icon)) 96 # Double-click is managed by KDE 97 98 elif TrayEngine == "AppIndicator": 99 self.menu = Gtk.Menu() 100 self.tray = AppIndicator.Indicator.new(name, icon, AppIndicator.IndicatorCategory.APPLICATION_STATUS) 101 self.tray.set_menu(self.menu) 102 # Double-click is not possible with App-Indicators 103 104 elif TrayEngine == "Qt": 105 self.menu = QMenu(parent) 106 self.tray = QSystemTrayIcon(getIcon(icon)) 107 self.tray.setContextMenu(self.menu) 108 self.tray.setParent(parent) 109 self.tray.activated.connect(self.qt_systray_clicked) 110 111 # ------------------------------------------------------------------------------------------- 112 113 def addAction(self, act_name_id, act_name_string, is_check=False): 114 if TrayEngine == "KDE": 115 act_widget = KAction(act_name_string, self.menu) 116 act_widget.setCheckable(is_check) 117 self.menu.addAction(act_widget) 118 119 elif TrayEngine == "AppIndicator": 120 if is_check: 121 act_widget = Gtk.CheckMenuItem(act_name_string) 122 else: 123 act_widget = Gtk.ImageMenuItem(act_name_string) 124 act_widget.set_image(None) 125 act_widget.show() 126 self.menu.append(act_widget) 127 128 elif TrayEngine == "Qt": 129 act_widget = QAction(act_name_string, self.menu) 130 act_widget.setCheckable(is_check) 131 self.menu.addAction(act_widget) 132 133 else: 134 act_widget = None 135 136 act_obj = [None, None, None, None] 137 act_obj[iActNameId] = act_name_id 138 act_obj[iActWidget] = act_widget 139 140 self.act_indexes.append(act_obj) 141 142 def addSeparator(self, sep_name_id): 143 if TrayEngine == "KDE": 144 sep_widget = self.menu.addSeparator() 145 146 elif TrayEngine == "AppIndicator": 147 sep_widget = Gtk.SeparatorMenuItem() 148 sep_widget.show() 149 self.menu.append(sep_widget) 150 151 elif TrayEngine == "Qt": 152 sep_widget = self.menu.addSeparator() 153 154 else: 155 sep_widget = None 156 157 sep_obj = [None, None, None] 158 sep_obj[iSepNameId] = sep_name_id 159 sep_obj[iSepWidget] = sep_widget 160 161 self.sep_indexes.append(sep_obj) 162 163 def addMenu(self, menu_name_id, menu_name_string): 164 if TrayEngine == "KDE": 165 menu_widget = KMenu(menu_name_string, self.menu) 166 self.menu.addMenu(menu_widget) 167 168 elif TrayEngine == "AppIndicator": 169 menu_widget = Gtk.MenuItem(menu_name_string) 170 menu_parent = Gtk.Menu() 171 menu_widget.set_submenu(menu_parent) 172 menu_widget.show() 173 self.menu.append(menu_widget) 174 175 elif TrayEngine == "Qt": 176 menu_widget = QMenu(menu_name_string, self.menu) 177 self.menu.addMenu(menu_widget) 178 179 else: 180 menu_widget = None 181 182 menu_obj = [None, None, None] 183 menu_obj[iMenuNameId] = menu_name_id 184 menu_obj[iMenuWidget] = menu_widget 185 186 self.menu_indexes.append(menu_obj) 187 188 # ------------------------------------------------------------------------------------------- 189 190 def addMenuAction(self, menu_name_id, act_name_id, act_name_string, is_check=False): 191 i = self.get_menu_index(menu_name_id) 192 if i < 0: return 193 194 menu_widget = self.menu_indexes[i][iMenuWidget] 195 196 if TrayEngine == "KDE": 197 act_widget = KAction(act_name_string, menu_widget) 198 act_widget.setCheckable(is_check) 199 menu_widget.addAction(act_widget) 200 201 elif TrayEngine == "AppIndicator": 202 menu_widget = menu_widget.get_submenu() 203 if is_check: 204 act_widget = Gtk.CheckMenuItem(act_name_string) 205 else: 206 act_widget = Gtk.ImageMenuItem(act_name_string) 207 act_widget.set_image(None) 208 act_widget.show() 209 menu_widget.append(act_widget) 210 211 elif TrayEngine == "Qt": 212 act_widget = QAction(act_name_string, menu_widget) 213 act_widget.setCheckable(is_check) 214 menu_widget.addAction(act_widget) 215 216 else: 217 act_widget = None 218 219 act_obj = [None, None, None, None] 220 act_obj[iActNameId] = act_name_id 221 act_obj[iActWidget] = act_widget 222 act_obj[iActParentMenuId] = menu_name_id 223 224 self.act_indexes.append(act_obj) 225 226 def addMenuSeparator(self, menu_name_id, sep_name_id): 227 i = self.get_menu_index(menu_name_id) 228 if i < 0: return 229 230 menu_widget = self.menu_indexes[i][iMenuWidget] 231 232 if TrayEngine == "KDE": 233 sep_widget = menu_widget.addSeparator() 234 235 elif TrayEngine == "AppIndicator": 236 menu_widget = menu_widget.get_submenu() 237 sep_widget = Gtk.SeparatorMenuItem() 238 sep_widget.show() 239 menu_widget.append(sep_widget) 240 241 elif TrayEngine == "Qt": 242 sep_widget = menu_widget.addSeparator() 243 244 else: 245 sep_widget = None 246 247 sep_obj = [None, None, None] 248 sep_obj[iSepNameId] = sep_name_id 249 sep_obj[iSepWidget] = sep_widget 250 sep_obj[iSepParentMenuId] = menu_name_id 251 252 self.sep_indexes.append(sep_obj) 253 254 #def addSubMenu(self, menu_name_id, new_menu_name_id, new_menu_name_string): 255 #menu_index = self.get_menu_index(menu_name_id) 256 #if menu_index < 0: return 257 #menu_widget = self.menu_indexes[menu_index][1] 258 ##if TrayEngine == "KDE": 259 ##new_menu_widget = KMenu(new_menu_name_string, self.menu) 260 ##menu_widget.addMenu(new_menu_widget) 261 ##elif TrayEngine == "AppIndicator": 262 ##new_menu_widget = Gtk.MenuItem(new_menu_name_string) 263 ##new_menu_widget.show() 264 ##menu_widget.get_submenu().append(new_menu_widget) 265 ##parent_menu_widget = Gtk.Menu() 266 ##new_menu_widget.set_submenu(parent_menu_widget) 267 ##else: 268 #if (1): 269 #new_menu_widget = QMenu(new_menu_name_string, self.menu) 270 #menu_widget.addMenu(new_menu_widget) 271 #self.menu_indexes.append([new_menu_name_id, new_menu_widget, menu_name_id]) 272 273 # ------------------------------------------------------------------------------------------- 274 275 def connect(self, act_name_id, act_func): 276 i = self.get_act_index(act_name_id) 277 if i < 0: return 278 279 act_widget = self.act_indexes[i][iActWidget] 280 281 if TrayEngine == "AppIndicator": 282 act_widget.connect("activate", self.gtk_call_func, act_name_id) 283 284 elif TrayEngine in ("KDE", "Qt"): 285 act_widget.triggered.connect(act_func) 286 287 self.act_indexes[i][iActFunc] = act_func 288 289 # ------------------------------------------------------------------------------------------- 290 291 #def setActionChecked(self, act_name_id, yesno): 292 #index = self.get_act_index(act_name_id) 293 #if index < 0: return 294 #act_widget = self.act_indexes[index][1] 295 ##if TrayEngine == "KDE": 296 ##act_widget.setChecked(yesno) 297 ##elif TrayEngine == "AppIndicator": 298 ##if type(act_widget) != Gtk.CheckMenuItem: 299 ##return # Cannot continue 300 ##act_widget.set_active(yesno) 301 ##else: 302 #if (1): 303 #act_widget.setChecked(yesno) 304 305 def setActionEnabled(self, act_name_id, yesno): 306 i = self.get_act_index(act_name_id) 307 if i < 0: return 308 309 act_widget = self.act_indexes[i][iActWidget] 310 311 if TrayEngine == "KDE": 312 act_widget.setEnabled(yesno) 313 314 elif TrayEngine == "AppIndicator": 315 act_widget.set_sensitive(yesno) 316 317 elif TrayEngine == "Qt": 318 act_widget.setEnabled(yesno) 319 320 def setActionIcon(self, act_name_id, icon): 321 i = self.get_act_index(act_name_id) 322 if i < 0: return 323 324 act_widget = self.act_indexes[i][iActWidget] 325 326 if TrayEngine == "KDE": 327 act_widget.setIcon(KIcon(icon)) 328 329 elif TrayEngine == "AppIndicator": 330 if not isinstance(act_widget, Gtk.ImageMenuItem): 331 # Cannot use icons here 332 return 333 334 act_widget.set_image(Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.MENU)) 335 #act_widget.set_always_show_image(True) 336 337 elif TrayEngine == "Qt": 338 act_widget.setIcon(getIcon(icon)) 339 340 def setActionText(self, act_name_id, text): 341 i = self.get_act_index(act_name_id) 342 if i < 0: return 343 344 act_widget = self.act_indexes[i][iActWidget] 345 346 if TrayEngine == "KDE": 347 act_widget.setText(text) 348 349 elif TrayEngine == "AppIndicator": 350 if isinstance(act_widget, Gtk.ImageMenuItem): 351 # Fix icon reset 352 last_icon = act_widget.get_image() 353 act_widget.set_label(text) 354 act_widget.set_image(last_icon) 355 else: 356 act_widget.set_label(text) 357 358 elif TrayEngine == "Qt": 359 act_widget.setText(text) 360 361 def setIcon(self, icon): 362 if TrayEngine == "KDE": 363 self.tray.setIconByPixmap(getIcon(icon)) 364 #self.tray.setToolTipIconByPixmap(getIcon(icon)) 365 366 elif TrayEngine == "AppIndicator": 367 self.tray.set_icon(icon) 368 369 elif TrayEngine == "Qt": 370 self.tray.setIcon(getIcon(icon)) 371 372 def setToolTip(self, text): 373 if TrayEngine == "KDE": 374 self.tray.setToolTipSubTitle(text) 375 376 elif TrayEngine == "AppIndicator": 377 # ToolTips are disabled in App-Indicators by design 378 pass 379 380 elif TrayEngine == "Qt": 381 self.tray.setToolTip(text) 382 383 # ------------------------------------------------------------------------------------------- 384 385 #def removeAction(self, act_name_id): 386 #index = self.get_act_index(act_name_id) 387 #if index < 0: return 388 #act_widget = self.act_indexes[index][1] 389 #parent_menu_widget = self.get_parent_menu_widget(self.act_indexes[index][2]) 390 ##if TrayEngine == "KDE": 391 ##parent_menu_widget.removeAction(act_widget) 392 ##elif TrayEngine == "AppIndicator": 393 ##act_widget.hide() 394 ##parent_menu_widget.remove(act_widget) 395 ##else: 396 #if (1): 397 #parent_menu_widget.removeAction(act_widget) 398 #self.act_indexes.pop(index) 399 400 #def removeSeparator(self, sep_name_id): 401 #index = self.get_sep_index(sep_name_id) 402 #if index < 0: return 403 #sep_widget = self.sep_indexes[index][1] 404 #parent_menu_widget = self.get_parent_menu_widget(self.sep_indexes[index][2]) 405 ##if TrayEngine == "KDE": 406 ##parent_menu_widget.removeAction(sep_widget) 407 ##elif TrayEngine == "AppIndicator": 408 ##sep_widget.hide() 409 ##parent_menu_widget.remove(sep_widget) 410 ##else: 411 #if (1): 412 #parent_menu_widget.removeAction(sep_widget) 413 #self.sep_indexes.pop(index) 414 415 #def removeMenu(self, menu_name_id): 416 #index = self.get_menu_index(menu_name_id) 417 #if index < 0: return 418 #menu_widget = self.menu_indexes[index][1] 419 #parent_menu_widget = self.get_parent_menu_widget(self.menu_indexes[index][2]) 420 ##if TrayEngine == "KDE": 421 ##parent_menu_widget.removeAction(menu_widget.menuAction()) 422 ##elif TrayEngine == "AppIndicator": 423 ##menu_widget.hide() 424 ##parent_menu_widget.remove(menu_widget.get_submenu()) 425 ##else: 426 #if (1): 427 #parent_menu_widget.removeAction(menu_widget.menuAction()) 428 #self.remove_actions_by_menu_name_id(menu_name_id) 429 #self.remove_separators_by_menu_name_id(menu_name_id) 430 #self.remove_submenus_by_menu_name_id(menu_name_id) 431 432 # ------------------------------------------------------------------------------------------- 433 434 #def clearAll(self): 435 ##if TrayEngine == "KDE": 436 ##self.menu.clear() 437 ##elif TrayEngine == "AppIndicator": 438 ##for child in self.menu.get_children(): 439 ##self.menu.remove(child) 440 ##else: 441 #if (1): 442 #self.menu.clear() 443 444 #self.act_indexes = [] 445 #self.sep_indexes = [] 446 #self.menu_indexes = [] 447 448 #def clearMenu(self, menu_name_id): 449 #menu_index = self.get_menu_index(menu_name_id) 450 #if menu_index < 0: return 451 #menu_widget = self.menu_indexes[menu_index][1] 452 ##if TrayEngine == "KDE": 453 ##menu_widget.clear() 454 ##elif TrayEngine == "AppIndicator": 455 ##for child in menu_widget.get_submenu().get_children(): 456 ##menu_widget.get_submenu().remove(child) 457 ##else: 458 #if (1): 459 #menu_widget.clear() 460 #list_of_submenus = [menu_name_id] 461 #for x in range(0, 10): # 10x level deep, should cover all cases... 462 #for this_menu_name_id, menu_widget, parent_menu_id in self.menu_indexes: 463 #if parent_menu_id in list_of_submenus and this_menu_name_id not in list_of_submenus: 464 #list_of_submenus.append(this_menu_name_id) 465 #for this_menu_name_id in list_of_submenus: 466 #self.remove_actions_by_menu_name_id(this_menu_name_id) 467 #self.remove_separators_by_menu_name_id(this_menu_name_id) 468 #self.remove_submenus_by_menu_name_id(this_menu_name_id) 469 470 # ------------------------------------------------------------------------------------------- 471 472 def getTrayEngine(self): 473 return TrayEngine 474 475 def isTrayAvailable(self): 476 if TrayEngine in ("KDE", "Qt"): 477 # Ask Qt 478 return QSystemTrayIcon.isSystemTrayAvailable() 479 480 if TrayEngine == "AppIndicator": 481 # Ubuntu/Unity always has a systray 482 return True 483 484 return False 485 486 def handleQtCloseEvent(self, event): 487 if self.isTrayAvailable() and self._parent.isVisible(): 488 event.accept() 489 self.__hideShowCall() 490 return 491 492 self.close() 493 QMainWindow.closeEvent(self._parent, event) 494 495 # ------------------------------------------------------------------------------------------- 496 497 def show(self): 498 if not self._quit_added: 499 self._quit_added = True 500 501 if TrayEngine != "KDE": 502 self.addSeparator("_quit") 503 self.addAction("show", self._parent.tr("Minimize")) 504 self.addAction("quit", self._parent.tr("Quit")) 505 self.setActionIcon("quit", "application-exit") 506 self.connect("show", self.__hideShowCall) 507 self.connect("quit", self.__quitCall) 508 509 if TrayEngine == "KDE": 510 self.tray.setStatus(KStatusNotifierItem.Active) 511 elif TrayEngine == "AppIndicator": 512 self.tray.set_status(AppIndicator.IndicatorStatus.ACTIVE) 513 elif TrayEngine == "Qt": 514 self.tray.show() 515 516 def hide(self): 517 if TrayEngine == "KDE": 518 self.tray.setStatus(KStatusNotifierItem.Passive) 519 elif TrayEngine == "AppIndicator": 520 self.tray.set_status(AppIndicator.IndicatorStatus.PASSIVE) 521 elif TrayEngine == "Qt": 522 self.tray.hide() 523 524 def close(self): 525 if TrayEngine == "KDE": 526 self.menu.close() 527 elif TrayEngine == "AppIndicator": 528 if self._gtk_running: 529 self._gtk_running = False 530 Gtk.main_quit() 531 elif TrayEngine == "Qt": 532 self.menu.close() 533 534 def exec_(self, app): 535 self._app = app 536 if TrayEngine == "AppIndicator": 537 self._gtk_running = True 538 return Gtk.main() 539 else: 540 return app.exec_() 541 542 # ------------------------------------------------------------------------------------------- 543 544 def get_act_index(self, act_name_id): 545 for i in range(len(self.act_indexes)): 546 if self.act_indexes[i][iActNameId] == act_name_id: 547 return i 548 else: 549 print("systray.py - Failed to get action index for %s" % act_name_id) 550 return -1 551 552 def get_sep_index(self, sep_name_id): 553 for i in range(len(self.sep_indexes)): 554 if self.sep_indexes[i][iSepNameId] == sep_name_id: 555 return i 556 else: 557 print("systray.py - Failed to get separator index for %s" % sep_name_id) 558 return -1 559 560 def get_menu_index(self, menu_name_id): 561 for i in range(len(self.menu_indexes)): 562 if self.menu_indexes[i][iMenuNameId] == menu_name_id: 563 return i 564 else: 565 print("systray.py - Failed to get menu index for %s" % menu_name_id) 566 return -1 567 568 #def get_parent_menu_widget(self, parent_menu_id): 569 #if parent_menu_id != None: 570 #menu_index = self.get_menu_index(parent_menu_id) 571 #if menu_index >= 0: 572 #return self.menu_indexes[menu_index][1] 573 #else: 574 #print("systray.py::Failed to get parent Menu widget for", parent_menu_id) 575 #return None 576 #else: 577 #return self.menu 578 579 #def remove_actions_by_menu_name_id(self, menu_name_id): 580 #h = 0 581 #for i in range(len(self.act_indexes)): 582 #act_name_id, act_widget, parent_menu_id, act_func = self.act_indexes[i - h] 583 #if parent_menu_id == menu_name_id: 584 #self.act_indexes.pop(i - h) 585 #h += 1 586 587 #def remove_separators_by_menu_name_id(self, menu_name_id): 588 #h = 0 589 #for i in range(len(self.sep_indexes)): 590 #sep_name_id, sep_widget, parent_menu_id = self.sep_indexes[i - h] 591 #if parent_menu_id == menu_name_id: 592 #self.sep_indexes.pop(i - h) 593 #h += 1 594 595 #def remove_submenus_by_menu_name_id(self, submenu_name_id): 596 #h = 0 597 #for i in range(len(self.menu_indexes)): 598 #menu_name_id, menu_widget, parent_menu_id = self.menu_indexes[i - h] 599 #if parent_menu_id == submenu_name_id: 600 #self.menu_indexes.pop(i - h) 601 #h += 1 602 603 # ------------------------------------------------------------------------------------------- 604 605 def gtk_call_func(self, gtkmenu, act_name_id): 606 i = self.get_act_index(act_name_id) 607 if i < 0: return None 608 609 return self.act_indexes[i][iActFunc] 610 611 def qt_systray_clicked(self, reason): 612 if reason in (QSystemTrayIcon.DoubleClick, QSystemTrayIcon.Trigger): 613 self.__hideShowCall() 614 615 # ------------------------------------------------------------------------------------------- 616 617 def __hideShowCall(self): 618 if self._parent.isVisible(): 619 self.setActionText("show", self._parent.tr("Restore")) 620 self._parent.hide() 621 622 if self._app: 623 self._app.setQuitOnLastWindowClosed(False) 624 625 else: 626 self.setActionText("show", self._parent.tr("Minimize")) 627 628 if self._parent.isMaximized(): 629 self._parent.showMaximized() 630 else: 631 self._parent.showNormal() 632 633 if self._app: 634 self._app.setQuitOnLastWindowClosed(True) 635 636 QTimer.singleShot(500, self.__raiseWindow) 637 638 def __quitCall(self): 639 if self._app: 640 self._app.setQuitOnLastWindowClosed(True) 641 642 self._parent.hide() 643 self._parent.close() 644 645 if self._app: 646 self._app.quit() 647 648 def __raiseWindow(self): 649 self._parent.activateWindow() 650 self._parent.raise_() 651 652#--------------- main ------------------ 653if __name__ == '__main__': 654 from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox 655 656 class ExampleGUI(QDialog): 657 def __init__(self, parent=None): 658 QDialog.__init__(self, parent) 659 660 self.setWindowIcon(getIcon("audacity")) 661 662 self.systray = GlobalSysTray(self, "Claudia", "claudia") 663 self.systray.addAction("about", self.tr("About")) 664 self.systray.setIcon("audacity") 665 self.systray.setToolTip("Demo systray app") 666 667 self.systray.connect("about", self.about) 668 669 self.systray.show() 670 671 def about(self): 672 QMessageBox.about(self, self.tr("About"), self.tr("Systray Demo")) 673 674 def done(self, r): 675 QDialog.done(self, r) 676 self.close() 677 678 def closeEvent(self, event): 679 self.systray.close() 680 QDialog.closeEvent(self, event) 681 682 app = QApplication(sys.argv) 683 gui = ExampleGUI() 684 gui.show() 685 sys.exit(gui.systray.exec_(app)) 686