From 1b1f1b0cc8eb336df6e9159f2ee5396a5f39dffb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Oct 2014 11:42:24 +0530 Subject: [PATCH] Add system tray to demo app --- src/calibre/gui2/dbus_export/demo.py | 37 +++++++++++++++++++++++-- src/calibre/gui2/dbus_export/menu.py | 15 ++++++---- src/calibre/gui2/dbus_export/tray.py | 12 +++++--- src/calibre/gui2/dbus_export/widgets.py | 15 +++++----- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/calibre/gui2/dbus_export/demo.py b/src/calibre/gui2/dbus_export/demo.py index 40c197ad63..32b16fed94 100644 --- a/src/calibre/gui2/dbus_export/demo.py +++ b/src/calibre/gui2/dbus_export/demo.py @@ -8,7 +8,7 @@ __copyright__ = '2014, Kovid Goyal ' 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_() diff --git a/src/calibre/gui2/dbus_export/menu.py b/src/calibre/gui2/dbus_export/menu.py index 2c2bdfc1a3..3057794254 100644 --- a/src/calibre/gui2/dbus_export/menu.py +++ b/src/calibre/gui2/dbus_export/menu.py @@ -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 diff --git a/src/calibre/gui2/dbus_export/tray.py b/src/calibre/gui2/dbus_export/tray.py index dba64e959d..954d1615b6 100644 --- a/src/calibre/gui2/dbus_export/tray.py +++ b/src/calibre/gui2/dbus_export/tray.py @@ -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): diff --git a/src/calibre/gui2/dbus_export/widgets.py b/src/calibre/gui2/dbus_export/widgets.py index 3ebefad0f9..2cc6b270be 100644 --- a/src/calibre/gui2/dbus_export/widgets.py +++ b/src/calibre/gui2/dbus_export/widgets.py @@ -8,7 +8,7 @@ __copyright__ = '2014, Kovid Goyal ' 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)