cadence/src/systray.py

689 lines
23 KiB
Python
Executable File

#!/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))