Add system tray to demo app

This commit is contained in:
Kovid Goyal 2014-10-29 11:42:24 +05:30
parent fce224af80
commit 1b1f1b0cc8
4 changed files with 59 additions and 20 deletions

View File

@ -8,7 +8,7 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
from PyQt5.Qt import (
QApplication, QMainWindow, QVBoxLayout, Qt, QKeySequence, QAction,
QActionGroup, QMenu)
QActionGroup, QMenu, QIcon)
from calibre.gui2.dbus_export.utils import setup_for_cli_run
from calibre.gui2.dbus_export.widgets import factory
@ -22,6 +22,7 @@ class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
f = factory()
self.setMinimumWidth(400)
self.setWindowTitle('Demo of DBUS menu exporter and systray integration')
self.statusBar().showMessage(self.windowTitle())
@ -32,6 +33,11 @@ class MainWindow(QMainWindow):
m = self.menu_one = mb.addMenu('&One')
m.aboutToShow.connect(self.about_to_show_one)
s = self.style()
self.q = q = QAction('&Quit', self)
q.setShortcut(QKeySequence.Quit)
q.triggered.connect(QApplication.quit)
self.addAction(q)
QApplication.instance().setWindowIcon(QIcon(I('debug.png')))
for i, icon in zip(xrange(3), map(s.standardIcon, (s.SP_DialogOkButton, s.SP_DialogCancelButton, s.SP_ArrowUp))):
ac = m.addAction('One - &%d' % (i + 1))
ac.setShortcut(QKeySequence(Qt.CTRL | (Qt.Key_1 + i), Qt.SHIFT | (Qt.Key_1 + i)))
@ -57,6 +63,32 @@ class MainWindow(QMainWindow):
ac.triggered.connect(self.action_triggered)
for m in mb.findChildren(QMenu):
m.aboutToShow.connect(self.about_to_show)
self.systray = f.create_system_tray_icon(parent=self, title=self.windowTitle())
if self.systray is not None:
self.systray.activated.connect(self.tray_activated)
self.sm = m = QMenu()
m.addAction('Show/hide main window').triggered.connect(self.tray_activated)
m.addAction(q)
self.systray.setContextMenu(m)
self.update_tray_toggle_action()
print ('DBUS connection unique name:', f.bus.get_unique_name())
def update_tray_toggle_action(self):
if hasattr(self, 'sm'):
self.sm.actions()[0].setText('Hide main window' if self.isVisible() else 'Show main window')
def hideEvent(self, ev):
if not ev.spontaneous():
self.update_tray_toggle_action()
return QMainWindow.hideEvent(self, ev)
def showEvent(self, ev):
if not ev.spontaneous():
self.update_tray_toggle_action()
return QMainWindow.showEvent(self, ev)
def tray_activated(self):
self.setVisible(not self.isVisible())
def action_triggered(self, checked=False):
ac = self.sender()
@ -74,8 +106,7 @@ class MainWindow(QMainWindow):
self.menu_two.addAction('Action added by about to show')
app = QApplication([])
f = factory()
app.setApplicationName('com.calibre-ebook.DBusExportDemo')
mw = MainWindow()
mw.show()
print ('DBUS connection unique name:', f.bus.get_unique_name())
app.exec_()

View File

@ -68,12 +68,12 @@ class DBusMenu(QObject):
handle_event_signal = pyqtSignal(object, object, object, object)
def __init__(self, object_path, **kw):
QObject.__init__(self, kw.get('parent'))
def __init__(self, object_path, parent=None, bus=None):
QObject.__init__(self, parent)
# Unity barfs is the Event DBUS method does not return immediately, so
# handle it asynchronously
self.handle_event_signal.connect(self.handle_event, type=Qt.QueuedConnection)
self.dbus_api = DBusMenuAPI(self, object_path, **kw)
self.dbus_api = DBusMenuAPI(self, object_path, bus=bus)
self.set_status = self.dbus_api.set_status
self._next_id = 0
self.action_changed_timer = t = QTimer(self)
@ -82,6 +82,10 @@ class DBusMenu(QObject):
t.setInterval(0), t.setSingleShot(True), t.timeout.connect(self.layouts_changed)
self.init_maps()
@property
def object_path(self):
return self.dbus_api._object_path
def init_maps(self, qmenu=None):
self.action_changes = set()
self.layout_changes = set()
@ -241,10 +245,9 @@ class DBusMenuAPI(Object):
IFACE = 'com.canonical.dbusmenu'
def __init__(self, menu, object_path, **kw):
bus = kw.get('bus')
def __init__(self, menu, object_path, bus=None):
if bus is None:
bus = kw['bus'] = dbus.SessionBus()
bus = dbus.SessionBus()
Object.__init__(self, bus, object_path)
self.status = 'normal'
self.menu = menu

View File

@ -16,7 +16,7 @@ import os
import dbus
from PyQt5.Qt import (
QApplication, QObject, pyqtSignal, Qt, QPoint, QRect, QMenu, QIcon)
QApplication, QObject, pyqtSignal, Qt, QPoint, QRect, QMenu)
from calibre.gui2.dbus_export.menu import DBusMenu
from calibre.gui2.dbus_export.utils import qicon_to_sni_image_list
@ -36,7 +36,7 @@ class StatusNotifierItem(QObject):
self.context_menu = None
self.is_visible = True
self.tool_tip = ''
self._icon = QIcon()
self._icon = QApplication.instance().windowIcon()
self.show_menu.connect(self._show_menu, type=Qt.QueuedConnection)
_sni_count += 1
kw['num'] = _sni_count
@ -85,6 +85,8 @@ class StatusNotifierItem(QObject):
def icon(self):
return self._icon
_status_item_menu_count = 0
class StatusNotifierItemAPI(Object):
'See http://www.notmart.org/misc/statusnotifieritem/statusnotifieritem.html'
@ -92,6 +94,7 @@ class StatusNotifierItemAPI(Object):
IFACE = 'org.kde.StatusNotifierItem'
def __init__(self, notifier, **kw):
global _status_item_menu_count
self.notifier = notifier
bus = kw.get('bus')
if bus is None:
@ -103,7 +106,8 @@ class StatusNotifierItemAPI(Object):
self.title = kw.get('title') or self.app_id
self.icon_serialization = qicon_to_sni_image_list(notifier.icon())
Object.__init__(self, bus, '/' + self.IFACE.split('.')[-1])
self.dbus_menu = DBusMenu('/StatusItemMenu', **kw)
_status_item_menu_count += 1
self.dbus_menu = DBusMenu('/StatusItemMenu/%d' % _status_item_menu_count, bus=bus, parent=kw.get('parent'))
def publish_new_menu(self):
menu = self.notifier.contextMenu()
@ -166,7 +170,7 @@ class StatusNotifierItemAPI(Object):
@dbus_property(IFACE, signature='o')
def Menu(self):
return dbus.ObjectPath(self.dbus_menu._object_path)
return dbus.ObjectPath(self.dbus_menu.object_path)
@dbus_property(IFACE, signature='i')
def WindowId(self):

View File

@ -8,7 +8,7 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import time, sys
from PyQt5.Qt import QObject, QMenuBar, QAction, QEvent, QSystemTrayIcon
from PyQt5.Qt import QObject, QMenuBar, QAction, QEvent, QSystemTrayIcon, QApplication
from calibre.constants import iswindows, isosx
@ -82,8 +82,9 @@ class ExportedMenuBar(QMenuBar):
class Factory(QObject):
def __init__(self):
def __init__(self, app_id=None):
QObject.__init__(self)
self.app_id = app_id or QApplication.instance().applicationName() or 'unknown_application'
if iswindows or isosx:
self.dbus = None
else:
@ -144,7 +145,7 @@ class Factory(QObject):
'See http://www.notmart.org/misc/statusnotifieritem/statusnotifierwatcher.html'
self.status_notifier = False
if self.bus.name_has_owner(STATUS_NOTIFIER[0]):
args = STATUS_NOTIFIER + ('Get', 'ss', (STATUS_NOTIFIER[-1], 'IsStatusNotifierHostRegistered'))
args = STATUS_NOTIFIER[:2] + (self.dbus.PROPERTIES_IFACE, 'Get', 'ss', (STATUS_NOTIFIER[-1], 'IsStatusNotifierHostRegistered'))
self.status_notifier = bool(self.bus.call_blocking(*args, timeout=0.1))
def create_window_menubar(self, parent):
@ -152,12 +153,12 @@ class Factory(QObject):
return ExportedMenuBar(parent, self.menu_registrar, self.bus)
return QMenuBar(parent)
def create_system_tray_icon(self, parent=None, title=None, app_id=None, category=None):
def create_system_tray_icon(self, parent=None, title=None, category=None):
if self.has_status_notifier:
from calibre.gui2.dbus_export.tray import StatusNotifierItem
ans = StatusNotifierItem(parent=parent, title=title, app_id=app_id, category=category)
self.bus.call_blocking(
self.SERVICE, self.PATH, self.IFACE, 'RegisterStatusNotifierItem', 's', (ans.dbus_api.name,), timeout=1)
ans = StatusNotifierItem(parent=parent, title=title, app_id=self.app_id, category=category)
args = STATUS_NOTIFIER + ('RegisterStatusNotifierItem', 's', (ans.dbus_api.name,))
self.bus.call_blocking(*args, timeout=1)
return ans
if iswindows or isosx:
return QSystemTrayIcon(parent)