|
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
-
- # KDE, App-Indicator or Qt Systray
- # Copyright (C) 2011-2018 Filipe Coelho <falktx@falktx.com>
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # For a full copy of the GNU General Public License see the COPYING file
-
- # Imports (Global)
- import os, sys
-
- if True:
- from PyQt5.QtCore import QTimer
- from PyQt5.QtGui import QIcon
- from PyQt5.QtWidgets import QAction, QMainWindow, QMenu, QSystemTrayIcon
- else:
- from PyQt4.QtCore import QTimer
- from PyQt4.QtGui import QIcon
- from PyQt4.QtGui import QAction, QMainWindow, QMenu, QSystemTrayIcon
-
- try:
- if False and os.getenv("DESKTOP_SESSION") in ("ubuntu", "ubuntu-2d") and not os.path.exists("/var/cadence/no_app_indicators"):
- from gi import require_version
- require_version('Gtk', '3.0')
- from gi.repository import Gtk
- require_version('AppIndicator3', '0.1')
- from gi.repository import AppIndicator3 as AppIndicator
- TrayEngine = "AppIndicator"
-
- #elif os.getenv("KDE_SESSION_VERSION") >= 5:
- #TrayEngine = "Qt"
-
- #elif os.getenv("KDE_FULL_SESSION") or os.getenv("DESKTOP_SESSION") == "kde-plasma":
- #from PyKDE5.kdeui import KAction, KIcon, KMenu, KStatusNotifierItem
- #TrayEngine = "KDE"
-
- else:
- TrayEngine = "Qt"
- except:
- TrayEngine = "Qt"
-
- from shared_i18n import *
-
- print("Using Tray Engine '%s'" % TrayEngine)
-
- iActNameId = 0
- iActWidget = 1
- iActParentMenuId = 2
- iActFunc = 3
-
- iSepNameId = 0
- iSepWidget = 1
- iSepParentMenuId = 2
-
- iMenuNameId = 0
- iMenuWidget = 1
- iMenuParentMenuId = 2
-
- # Get Icon from user theme, using our own as backup (Oxygen)
- def getIcon(icon, size=16):
- return QIcon.fromTheme(icon, QIcon(":/%ix%i/%s.png" % (size, size, icon)))
-
- # Global Systray class
- class GlobalSysTray(object):
- def __init__(self, parent, name, icon):
- object.__init__(self)
-
- self._app = None
- self._parent = parent
- self._gtk_running = False
- self._quit_added = False
-
- self.act_indexes = []
- self.sep_indexes = []
- self.menu_indexes = []
-
- if TrayEngine == "KDE":
- self.menu = KMenu(parent)
- self.menu.setTitle(name)
- self.tray = KStatusNotifierItem()
- self.tray.setAssociatedWidget(parent)
- self.tray.setCategory(KStatusNotifierItem.ApplicationStatus)
- self.tray.setContextMenu(self.menu)
- self.tray.setIconByPixmap(getIcon(icon))
- self.tray.setTitle(name)
- self.tray.setToolTipTitle(" ")
- self.tray.setToolTipIconByPixmap(getIcon(icon))
- # Double-click is managed by KDE
-
- elif TrayEngine == "AppIndicator":
- self.menu = Gtk.Menu()
- self.tray = AppIndicator.Indicator.new(name, icon, AppIndicator.IndicatorCategory.APPLICATION_STATUS)
- self.tray.set_menu(self.menu)
- # Double-click is not possible with App-Indicators
-
- elif TrayEngine == "Qt":
- self.menu = QMenu(parent)
- self.tray = QSystemTrayIcon(getIcon(icon))
- self.tray.setContextMenu(self.menu)
- self.tray.setParent(parent)
- self.tray.activated.connect(self.qt_systray_clicked)
-
- # -------------------------------------------------------------------------------------------
-
- def addAction(self, act_name_id, act_name_string, is_check=False):
- if TrayEngine == "KDE":
- act_widget = KAction(act_name_string, self.menu)
- act_widget.setCheckable(is_check)
- self.menu.addAction(act_widget)
-
- elif TrayEngine == "AppIndicator":
- if is_check:
- act_widget = Gtk.CheckMenuItem(act_name_string)
- else:
- act_widget = Gtk.ImageMenuItem(act_name_string)
- act_widget.set_image(None)
- act_widget.show()
- self.menu.append(act_widget)
-
- elif TrayEngine == "Qt":
- act_widget = QAction(act_name_string, self.menu)
- act_widget.setCheckable(is_check)
- self.menu.addAction(act_widget)
-
- else:
- act_widget = None
-
- act_obj = [None, None, None, None]
- act_obj[iActNameId] = act_name_id
- act_obj[iActWidget] = act_widget
-
- self.act_indexes.append(act_obj)
-
- def addSeparator(self, sep_name_id):
- if TrayEngine == "KDE":
- sep_widget = self.menu.addSeparator()
-
- elif TrayEngine == "AppIndicator":
- sep_widget = Gtk.SeparatorMenuItem()
- sep_widget.show()
- self.menu.append(sep_widget)
-
- elif TrayEngine == "Qt":
- sep_widget = self.menu.addSeparator()
-
- else:
- sep_widget = None
-
- sep_obj = [None, None, None]
- sep_obj[iSepNameId] = sep_name_id
- sep_obj[iSepWidget] = sep_widget
-
- self.sep_indexes.append(sep_obj)
-
- def addMenu(self, menu_name_id, menu_name_string):
- if TrayEngine == "KDE":
- menu_widget = KMenu(menu_name_string, self.menu)
- self.menu.addMenu(menu_widget)
-
- elif TrayEngine == "AppIndicator":
- menu_widget = Gtk.MenuItem(menu_name_string)
- menu_parent = Gtk.Menu()
- menu_widget.set_submenu(menu_parent)
- menu_widget.show()
- self.menu.append(menu_widget)
-
- elif TrayEngine == "Qt":
- menu_widget = QMenu(menu_name_string, self.menu)
- self.menu.addMenu(menu_widget)
-
- else:
- menu_widget = None
-
- menu_obj = [None, None, None]
- menu_obj[iMenuNameId] = menu_name_id
- menu_obj[iMenuWidget] = menu_widget
-
- self.menu_indexes.append(menu_obj)
-
- # -------------------------------------------------------------------------------------------
-
- def addMenuAction(self, menu_name_id, act_name_id, act_name_string, is_check=False):
- i = self.get_menu_index(menu_name_id)
- if i < 0: return
-
- menu_widget = self.menu_indexes[i][iMenuWidget]
-
- if TrayEngine == "KDE":
- act_widget = KAction(act_name_string, menu_widget)
- act_widget.setCheckable(is_check)
- menu_widget.addAction(act_widget)
-
- elif TrayEngine == "AppIndicator":
- menu_widget = menu_widget.get_submenu()
- if is_check:
- act_widget = Gtk.CheckMenuItem(act_name_string)
- else:
- act_widget = Gtk.ImageMenuItem(act_name_string)
- act_widget.set_image(None)
- act_widget.show()
- menu_widget.append(act_widget)
-
- elif TrayEngine == "Qt":
- act_widget = QAction(act_name_string, menu_widget)
- act_widget.setCheckable(is_check)
- menu_widget.addAction(act_widget)
-
- else:
- act_widget = None
-
- act_obj = [None, None, None, None]
- act_obj[iActNameId] = act_name_id
- act_obj[iActWidget] = act_widget
- act_obj[iActParentMenuId] = menu_name_id
-
- self.act_indexes.append(act_obj)
-
- def addMenuSeparator(self, menu_name_id, sep_name_id):
- i = self.get_menu_index(menu_name_id)
- if i < 0: return
-
- menu_widget = self.menu_indexes[i][iMenuWidget]
-
- if TrayEngine == "KDE":
- sep_widget = menu_widget.addSeparator()
-
- elif TrayEngine == "AppIndicator":
- menu_widget = menu_widget.get_submenu()
- sep_widget = Gtk.SeparatorMenuItem()
- sep_widget.show()
- menu_widget.append(sep_widget)
-
- elif TrayEngine == "Qt":
- sep_widget = menu_widget.addSeparator()
-
- else:
- sep_widget = None
-
- sep_obj = [None, None, None]
- sep_obj[iSepNameId] = sep_name_id
- sep_obj[iSepWidget] = sep_widget
- sep_obj[iSepParentMenuId] = menu_name_id
-
- self.sep_indexes.append(sep_obj)
-
- #def addSubMenu(self, menu_name_id, new_menu_name_id, new_menu_name_string):
- #menu_index = self.get_menu_index(menu_name_id)
- #if menu_index < 0: return
- #menu_widget = self.menu_indexes[menu_index][1]
- ##if TrayEngine == "KDE":
- ##new_menu_widget = KMenu(new_menu_name_string, self.menu)
- ##menu_widget.addMenu(new_menu_widget)
- ##elif TrayEngine == "AppIndicator":
- ##new_menu_widget = Gtk.MenuItem(new_menu_name_string)
- ##new_menu_widget.show()
- ##menu_widget.get_submenu().append(new_menu_widget)
- ##parent_menu_widget = Gtk.Menu()
- ##new_menu_widget.set_submenu(parent_menu_widget)
- ##else:
- #if (1):
- #new_menu_widget = QMenu(new_menu_name_string, self.menu)
- #menu_widget.addMenu(new_menu_widget)
- #self.menu_indexes.append([new_menu_name_id, new_menu_widget, menu_name_id])
-
- # -------------------------------------------------------------------------------------------
-
- def connect(self, act_name_id, act_func):
- i = self.get_act_index(act_name_id)
- if i < 0: return
-
- act_widget = self.act_indexes[i][iActWidget]
-
- if TrayEngine == "AppIndicator":
- act_widget.connect("activate", self.gtk_call_func, act_name_id)
-
- elif TrayEngine in ("KDE", "Qt"):
- act_widget.triggered.connect(act_func)
-
- self.act_indexes[i][iActFunc] = act_func
-
- # -------------------------------------------------------------------------------------------
-
- #def setActionChecked(self, act_name_id, yesno):
- #index = self.get_act_index(act_name_id)
- #if index < 0: return
- #act_widget = self.act_indexes[index][1]
- ##if TrayEngine == "KDE":
- ##act_widget.setChecked(yesno)
- ##elif TrayEngine == "AppIndicator":
- ##if type(act_widget) != Gtk.CheckMenuItem:
- ##return # Cannot continue
- ##act_widget.set_active(yesno)
- ##else:
- #if (1):
- #act_widget.setChecked(yesno)
-
- def setActionEnabled(self, act_name_id, yesno):
- i = self.get_act_index(act_name_id)
- if i < 0: return
-
- act_widget = self.act_indexes[i][iActWidget]
-
- if TrayEngine == "KDE":
- act_widget.setEnabled(yesno)
-
- elif TrayEngine == "AppIndicator":
- act_widget.set_sensitive(yesno)
-
- elif TrayEngine == "Qt":
- act_widget.setEnabled(yesno)
-
- def setActionIcon(self, act_name_id, icon):
- i = self.get_act_index(act_name_id)
- if i < 0: return
-
- act_widget = self.act_indexes[i][iActWidget]
-
- if TrayEngine == "KDE":
- act_widget.setIcon(KIcon(icon))
-
- elif TrayEngine == "AppIndicator":
- if not isinstance(act_widget, Gtk.ImageMenuItem):
- # Cannot use icons here
- return
-
- act_widget.set_image(Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.MENU))
- #act_widget.set_always_show_image(True)
-
- elif TrayEngine == "Qt":
- act_widget.setIcon(getIcon(icon))
-
- def setActionText(self, act_name_id, text):
- i = self.get_act_index(act_name_id)
- if i < 0: return
-
- act_widget = self.act_indexes[i][iActWidget]
-
- if TrayEngine == "KDE":
- act_widget.setText(text)
-
- elif TrayEngine == "AppIndicator":
- if isinstance(act_widget, Gtk.ImageMenuItem):
- # Fix icon reset
- last_icon = act_widget.get_image()
- act_widget.set_label(text)
- act_widget.set_image(last_icon)
- else:
- act_widget.set_label(text)
-
- elif TrayEngine == "Qt":
- act_widget.setText(text)
-
- def setIcon(self, icon):
- if TrayEngine == "KDE":
- self.tray.setIconByPixmap(getIcon(icon))
- #self.tray.setToolTipIconByPixmap(getIcon(icon))
-
- elif TrayEngine == "AppIndicator":
- self.tray.set_icon(icon)
-
- elif TrayEngine == "Qt":
- self.tray.setIcon(getIcon(icon))
-
- def setToolTip(self, text):
- if TrayEngine == "KDE":
- self.tray.setToolTipSubTitle(text)
-
- elif TrayEngine == "AppIndicator":
- # ToolTips are disabled in App-Indicators by design
- pass
-
- elif TrayEngine == "Qt":
- self.tray.setToolTip(text)
-
- # -------------------------------------------------------------------------------------------
-
- #def removeAction(self, act_name_id):
- #index = self.get_act_index(act_name_id)
- #if index < 0: return
- #act_widget = self.act_indexes[index][1]
- #parent_menu_widget = self.get_parent_menu_widget(self.act_indexes[index][2])
- ##if TrayEngine == "KDE":
- ##parent_menu_widget.removeAction(act_widget)
- ##elif TrayEngine == "AppIndicator":
- ##act_widget.hide()
- ##parent_menu_widget.remove(act_widget)
- ##else:
- #if (1):
- #parent_menu_widget.removeAction(act_widget)
- #self.act_indexes.pop(index)
-
- #def removeSeparator(self, sep_name_id):
- #index = self.get_sep_index(sep_name_id)
- #if index < 0: return
- #sep_widget = self.sep_indexes[index][1]
- #parent_menu_widget = self.get_parent_menu_widget(self.sep_indexes[index][2])
- ##if TrayEngine == "KDE":
- ##parent_menu_widget.removeAction(sep_widget)
- ##elif TrayEngine == "AppIndicator":
- ##sep_widget.hide()
- ##parent_menu_widget.remove(sep_widget)
- ##else:
- #if (1):
- #parent_menu_widget.removeAction(sep_widget)
- #self.sep_indexes.pop(index)
-
- #def removeMenu(self, menu_name_id):
- #index = self.get_menu_index(menu_name_id)
- #if index < 0: return
- #menu_widget = self.menu_indexes[index][1]
- #parent_menu_widget = self.get_parent_menu_widget(self.menu_indexes[index][2])
- ##if TrayEngine == "KDE":
- ##parent_menu_widget.removeAction(menu_widget.menuAction())
- ##elif TrayEngine == "AppIndicator":
- ##menu_widget.hide()
- ##parent_menu_widget.remove(menu_widget.get_submenu())
- ##else:
- #if (1):
- #parent_menu_widget.removeAction(menu_widget.menuAction())
- #self.remove_actions_by_menu_name_id(menu_name_id)
- #self.remove_separators_by_menu_name_id(menu_name_id)
- #self.remove_submenus_by_menu_name_id(menu_name_id)
-
- # -------------------------------------------------------------------------------------------
-
- #def clearAll(self):
- ##if TrayEngine == "KDE":
- ##self.menu.clear()
- ##elif TrayEngine == "AppIndicator":
- ##for child in self.menu.get_children():
- ##self.menu.remove(child)
- ##else:
- #if (1):
- #self.menu.clear()
-
- #self.act_indexes = []
- #self.sep_indexes = []
- #self.menu_indexes = []
-
- #def clearMenu(self, menu_name_id):
- #menu_index = self.get_menu_index(menu_name_id)
- #if menu_index < 0: return
- #menu_widget = self.menu_indexes[menu_index][1]
- ##if TrayEngine == "KDE":
- ##menu_widget.clear()
- ##elif TrayEngine == "AppIndicator":
- ##for child in menu_widget.get_submenu().get_children():
- ##menu_widget.get_submenu().remove(child)
- ##else:
- #if (1):
- #menu_widget.clear()
- #list_of_submenus = [menu_name_id]
- #for x in range(0, 10): # 10x level deep, should cover all cases...
- #for this_menu_name_id, menu_widget, parent_menu_id in self.menu_indexes:
- #if parent_menu_id in list_of_submenus and this_menu_name_id not in list_of_submenus:
- #list_of_submenus.append(this_menu_name_id)
- #for this_menu_name_id in list_of_submenus:
- #self.remove_actions_by_menu_name_id(this_menu_name_id)
- #self.remove_separators_by_menu_name_id(this_menu_name_id)
- #self.remove_submenus_by_menu_name_id(this_menu_name_id)
-
- # -------------------------------------------------------------------------------------------
-
- def getTrayEngine(self):
- return TrayEngine
-
- def isTrayAvailable(self):
- if TrayEngine in ("KDE", "Qt"):
- # Ask Qt
- return QSystemTrayIcon.isSystemTrayAvailable()
-
- if TrayEngine == "AppIndicator":
- # Ubuntu/Unity always has a systray
- return True
-
- return False
-
- def handleQtCloseEvent(self, event):
- if self.isTrayAvailable() and self._parent.isVisible():
- event.accept()
- self.__hideShowCall()
- return
-
- self.close()
- QMainWindow.closeEvent(self._parent, event)
-
- # -------------------------------------------------------------------------------------------
-
- def show(self):
- if not self._quit_added:
- self._quit_added = True
-
- if TrayEngine != "KDE":
- self.addSeparator("_quit")
- self.addAction("show", self._parent.tr("Minimize"))
- self.addAction("quit", self._parent.tr("Quit"))
- self.setActionIcon("quit", "application-exit")
- self.connect("show", self.__hideShowCall)
- self.connect("quit", self.__quitCall)
-
- if TrayEngine == "KDE":
- self.tray.setStatus(KStatusNotifierItem.Active)
- elif TrayEngine == "AppIndicator":
- self.tray.set_status(AppIndicator.IndicatorStatus.ACTIVE)
- elif TrayEngine == "Qt":
- self.tray.show()
-
- def hide(self):
- if TrayEngine == "KDE":
- self.tray.setStatus(KStatusNotifierItem.Passive)
- elif TrayEngine == "AppIndicator":
- self.tray.set_status(AppIndicator.IndicatorStatus.PASSIVE)
- elif TrayEngine == "Qt":
- self.tray.hide()
-
- def close(self):
- if TrayEngine == "KDE":
- self.menu.close()
- elif TrayEngine == "AppIndicator":
- if self._gtk_running:
- self._gtk_running = False
- Gtk.main_quit()
- elif TrayEngine == "Qt":
- self.menu.close()
-
- def exec_(self, app):
- self._app = app
- if TrayEngine == "AppIndicator":
- self._gtk_running = True
- return Gtk.main()
- else:
- return app.exec_()
-
- # -------------------------------------------------------------------------------------------
-
- def get_act_index(self, act_name_id):
- for i in range(len(self.act_indexes)):
- if self.act_indexes[i][iActNameId] == act_name_id:
- return i
- else:
- print("systray.py - Failed to get action index for %s" % act_name_id)
- return -1
-
- def get_sep_index(self, sep_name_id):
- for i in range(len(self.sep_indexes)):
- if self.sep_indexes[i][iSepNameId] == sep_name_id:
- return i
- else:
- print("systray.py - Failed to get separator index for %s" % sep_name_id)
- return -1
-
- def get_menu_index(self, menu_name_id):
- for i in range(len(self.menu_indexes)):
- if self.menu_indexes[i][iMenuNameId] == menu_name_id:
- return i
- else:
- print("systray.py - Failed to get menu index for %s" % menu_name_id)
- return -1
-
- #def get_parent_menu_widget(self, parent_menu_id):
- #if parent_menu_id != None:
- #menu_index = self.get_menu_index(parent_menu_id)
- #if menu_index >= 0:
- #return self.menu_indexes[menu_index][1]
- #else:
- #print("systray.py::Failed to get parent Menu widget for", parent_menu_id)
- #return None
- #else:
- #return self.menu
-
- #def remove_actions_by_menu_name_id(self, menu_name_id):
- #h = 0
- #for i in range(len(self.act_indexes)):
- #act_name_id, act_widget, parent_menu_id, act_func = self.act_indexes[i - h]
- #if parent_menu_id == menu_name_id:
- #self.act_indexes.pop(i - h)
- #h += 1
-
- #def remove_separators_by_menu_name_id(self, menu_name_id):
- #h = 0
- #for i in range(len(self.sep_indexes)):
- #sep_name_id, sep_widget, parent_menu_id = self.sep_indexes[i - h]
- #if parent_menu_id == menu_name_id:
- #self.sep_indexes.pop(i - h)
- #h += 1
-
- #def remove_submenus_by_menu_name_id(self, submenu_name_id):
- #h = 0
- #for i in range(len(self.menu_indexes)):
- #menu_name_id, menu_widget, parent_menu_id = self.menu_indexes[i - h]
- #if parent_menu_id == submenu_name_id:
- #self.menu_indexes.pop(i - h)
- #h += 1
-
- # -------------------------------------------------------------------------------------------
-
- def gtk_call_func(self, gtkmenu, act_name_id):
- i = self.get_act_index(act_name_id)
- if i < 0: return None
-
- return self.act_indexes[i][iActFunc]
-
- def qt_systray_clicked(self, reason):
- if reason in (QSystemTrayIcon.DoubleClick, QSystemTrayIcon.Trigger):
- self.__hideShowCall()
-
- # -------------------------------------------------------------------------------------------
-
- def __hideShowCall(self):
- if self._parent.isVisible():
- self.setActionText("show", self._parent.tr("Restore"))
- self._parent.hide()
-
- if self._app:
- self._app.setQuitOnLastWindowClosed(False)
-
- else:
- self.setActionText("show", self._parent.tr("Minimize"))
-
- if self._parent.isMaximized():
- self._parent.showMaximized()
- else:
- self._parent.showNormal()
-
- if self._app:
- self._app.setQuitOnLastWindowClosed(True)
-
- QTimer.singleShot(500, self.__raiseWindow)
-
- def __quitCall(self):
- if self._app:
- self._app.setQuitOnLastWindowClosed(True)
-
- self._parent.hide()
- self._parent.close()
-
- if self._app:
- self._app.quit()
-
- def __raiseWindow(self):
- self._parent.activateWindow()
- self._parent.raise_()
-
- #--------------- main ------------------
- if __name__ == '__main__':
- from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
-
- class ExampleGUI(QDialog):
- def __init__(self, parent=None):
- QDialog.__init__(self, parent)
-
- self.setWindowIcon(getIcon("audacity"))
-
- self.systray = GlobalSysTray(self, "Claudia", "claudia")
- self.systray.addAction("about", self.tr("About"))
- self.systray.setIcon("audacity")
- self.systray.setToolTip("Demo systray app")
-
- self.systray.connect("about", self.about)
-
- self.systray.show()
-
- def about(self):
- QMessageBox.about(self, self.tr("About"), self.tr("Systray Demo"))
-
- def done(self, r):
- QDialog.done(self, r)
- self.close()
-
- def closeEvent(self, event):
- self.systray.close()
- QDialog.closeEvent(self, event)
-
- app = QApplication(sys.argv)
- setup_i18n()
- gui = ExampleGUI()
- gui.show()
- sys.exit(gui.systray.exec_(app))
|