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 ( from PyQt5.Qt import (
QApplication, QMainWindow, QVBoxLayout, Qt, QKeySequence, QAction, 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.utils import setup_for_cli_run
from calibre.gui2.dbus_export.widgets import factory from calibre.gui2.dbus_export.widgets import factory
@ -22,6 +22,7 @@ class MainWindow(QMainWindow):
def __init__(self): def __init__(self):
QMainWindow.__init__(self) QMainWindow.__init__(self)
f = factory()
self.setMinimumWidth(400) self.setMinimumWidth(400)
self.setWindowTitle('Demo of DBUS menu exporter and systray integration') self.setWindowTitle('Demo of DBUS menu exporter and systray integration')
self.statusBar().showMessage(self.windowTitle()) self.statusBar().showMessage(self.windowTitle())
@ -32,6 +33,11 @@ class MainWindow(QMainWindow):
m = self.menu_one = mb.addMenu('&One') m = self.menu_one = mb.addMenu('&One')
m.aboutToShow.connect(self.about_to_show_one) m.aboutToShow.connect(self.about_to_show_one)
s = self.style() 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))): 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 = m.addAction('One - &%d' % (i + 1))
ac.setShortcut(QKeySequence(Qt.CTRL | (Qt.Key_1 + i), Qt.SHIFT | (Qt.Key_1 + i))) 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) ac.triggered.connect(self.action_triggered)
for m in mb.findChildren(QMenu): for m in mb.findChildren(QMenu):
m.aboutToShow.connect(self.about_to_show) 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): def action_triggered(self, checked=False):
ac = self.sender() ac = self.sender()
@ -74,8 +106,7 @@ class MainWindow(QMainWindow):
self.menu_two.addAction('Action added by about to show') self.menu_two.addAction('Action added by about to show')
app = QApplication([]) app = QApplication([])
f = factory() app.setApplicationName('com.calibre-ebook.DBusExportDemo')
mw = MainWindow() mw = MainWindow()
mw.show() mw.show()
print ('DBUS connection unique name:', f.bus.get_unique_name())
app.exec_() app.exec_()

View File

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

View File

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

View File

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