mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Refactor StatusNotifierItem to uses the global DBus export factory
This commit is contained in:
parent
bd9d3b5478
commit
fce224af80
@ -19,112 +19,28 @@ from PyQt5.Qt import (
|
|||||||
QApplication, QObject, pyqtSignal, Qt, QPoint, QRect, QMenu, QIcon)
|
QApplication, QObject, pyqtSignal, Qt, QPoint, QRect, QMenu, QIcon)
|
||||||
|
|
||||||
from calibre.gui2.dbus_export.menu import DBusMenu
|
from calibre.gui2.dbus_export.menu import DBusMenu
|
||||||
from calibre.gui2.dbus_export.utils import log, qicon_to_sni_image_list, setup_for_cli_run
|
from calibre.gui2.dbus_export.utils import qicon_to_sni_image_list
|
||||||
from calibre.utils.dbus_service import Object, method as dbus_method, BusName, dbus_property, signal as dbus_signal
|
from calibre.utils.dbus_service import Object, method as dbus_method, BusName, dbus_property, signal as dbus_signal
|
||||||
|
|
||||||
class Factory(QObject):
|
_sni_count = 0
|
||||||
|
|
||||||
'See http://www.notmart.org/misc/statusnotifieritem/statusnotifierwatcher.html'
|
|
||||||
|
|
||||||
SERVICE = "org.kde.StatusNotifierWatcher"
|
|
||||||
PATH = "/StatusNotifierWatcher"
|
|
||||||
IFACE = "org.kde.StatusNotifierWatcher"
|
|
||||||
|
|
||||||
available_changed = pyqtSignal(bool)
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
QObject.__init__(self, parent)
|
|
||||||
self.count = 0
|
|
||||||
self.items = []
|
|
||||||
self.is_available = False
|
|
||||||
self._bus = None
|
|
||||||
self.connect_to_snw()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def bus(self):
|
|
||||||
if self._bus is None:
|
|
||||||
self._bus = dbus.SessionBus()
|
|
||||||
return self._bus
|
|
||||||
|
|
||||||
def bus_disconnected(self, conn):
|
|
||||||
self._bus = None
|
|
||||||
|
|
||||||
def init_bus(self):
|
|
||||||
bus = self.bus
|
|
||||||
bus.call_on_disconnection(self.bus_disconnected)
|
|
||||||
bus.watch_name_owner(self.SERVICE, self.owner_changed)
|
|
||||||
self.connect_to_snw()
|
|
||||||
|
|
||||||
def owner_changed(self, new_owner):
|
|
||||||
old = self.is_available
|
|
||||||
if new_owner:
|
|
||||||
self.connect_to_snw()
|
|
||||||
else:
|
|
||||||
self.is_available = False
|
|
||||||
if old != self.is_available:
|
|
||||||
self.available_changed.emit(self.is_available)
|
|
||||||
|
|
||||||
def connect_to_snw(self):
|
|
||||||
self.is_available = False
|
|
||||||
try:
|
|
||||||
self.bus.add_signal_receiver(self.host_registered, 'StatusNotifierHostRegistered', self.IFACE, self.SERVICE, self.PATH)
|
|
||||||
except dbus.DBusException as err:
|
|
||||||
log('Failed to connect to StatusNotifierHostRegistered, with error:', str(err))
|
|
||||||
|
|
||||||
self.update_availability()
|
|
||||||
if self.is_available:
|
|
||||||
for item in self.items:
|
|
||||||
self.register(item)
|
|
||||||
|
|
||||||
def update_availability(self):
|
|
||||||
try:
|
|
||||||
self.is_available = bool(self.bus.call_blocking(
|
|
||||||
self.SERVICE, self.PATH, dbus.PROPERTIES_IFACE, 'Get', 'ss', (self.IFACE, 'IsStatusNotifierHostRegistered'), timeout=0.1))
|
|
||||||
except dbus.DBusException as err:
|
|
||||||
self.is_available = False
|
|
||||||
log('Failed to get StatusNotifier host availability with error:', str(err))
|
|
||||||
|
|
||||||
def host_registered(self, *args):
|
|
||||||
if not self.is_available:
|
|
||||||
self.is_available = True
|
|
||||||
self.available_changed.emit(self.is_available)
|
|
||||||
|
|
||||||
def create_indicator(self, **kw):
|
|
||||||
if not self.is_available:
|
|
||||||
raise RuntimeError('StatusNotifier services are not available on this system')
|
|
||||||
self.count += 1
|
|
||||||
kw['bus'] = self.bus
|
|
||||||
item = StatusNotifierItem(self.count, **kw)
|
|
||||||
self.items.append(item)
|
|
||||||
item.destroyed.connect(self.items.remove)
|
|
||||||
self.register(item)
|
|
||||||
return item
|
|
||||||
|
|
||||||
def register(self, item):
|
|
||||||
self.bus.call_blocking(
|
|
||||||
self.SERVICE, self.PATH, self.IFACE, 'RegisterStatusNotifierItem', 's', (item.dbus.name,), timeout=0.1)
|
|
||||||
|
|
||||||
class StatusNotifierItem(QObject):
|
class StatusNotifierItem(QObject):
|
||||||
|
|
||||||
IFACE = 'org.kde.StatusNotifierItem'
|
IFACE = 'org.kde.StatusNotifierItem'
|
||||||
NewTitle = pyqtSignal()
|
|
||||||
NewIcon = pyqtSignal()
|
|
||||||
NewAttentionIcon = pyqtSignal()
|
|
||||||
NewOverlayIcon = pyqtSignal()
|
|
||||||
NewToolTip = pyqtSignal()
|
|
||||||
NewStatus = pyqtSignal(str)
|
|
||||||
activated = pyqtSignal()
|
activated = pyqtSignal()
|
||||||
show_menu = pyqtSignal(int, int)
|
show_menu = pyqtSignal(int, int)
|
||||||
|
|
||||||
def __init__(self, num, **kw):
|
def __init__(self, **kw):
|
||||||
|
global _sni_count
|
||||||
QObject.__init__(self, parent=kw.get('parent'))
|
QObject.__init__(self, parent=kw.get('parent'))
|
||||||
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 = QIcon()
|
||||||
self.show_menu.connect(self._show_menu, type=Qt.QueuedConnection)
|
self.show_menu.connect(self._show_menu, type=Qt.QueuedConnection)
|
||||||
kw['num'] = num
|
_sni_count += 1
|
||||||
self.dbus = StatusNotifierItemAPI(self, **kw)
|
kw['num'] = _sni_count
|
||||||
|
self.dbus_api = StatusNotifierItemAPI(self, **kw)
|
||||||
|
|
||||||
def _show_menu(self, x, y):
|
def _show_menu(self, x, y):
|
||||||
m = self.contextMenu()
|
m = self.contextMenu()
|
||||||
@ -137,7 +53,7 @@ class StatusNotifierItem(QObject):
|
|||||||
def setVisible(self, visible):
|
def setVisible(self, visible):
|
||||||
if self.is_visible != visible:
|
if self.is_visible != visible:
|
||||||
self.is_visible = visible
|
self.is_visible = visible
|
||||||
self.NewStatus.emit(self.dbus.Status)
|
self.dbus_api.NewStatus(self.dbus_api.Status)
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
self.setVisible(True)
|
self.setVisible(True)
|
||||||
@ -150,7 +66,7 @@ class StatusNotifierItem(QObject):
|
|||||||
|
|
||||||
def setContextMenu(self, menu):
|
def setContextMenu(self, menu):
|
||||||
self.context_menu = menu
|
self.context_menu = menu
|
||||||
self.dbus.publish_new_menu()
|
self.dbus_api.publish_new_menu()
|
||||||
|
|
||||||
def geometry(self):
|
def geometry(self):
|
||||||
return QRect()
|
return QRect()
|
||||||
@ -160,11 +76,11 @@ class StatusNotifierItem(QObject):
|
|||||||
|
|
||||||
def setToolTip(self, val):
|
def setToolTip(self, val):
|
||||||
self.tool_tip = val or ''
|
self.tool_tip = val or ''
|
||||||
self.NewToolTip.emit()
|
self.dbus_api.NewToolTip()
|
||||||
|
|
||||||
def setIcon(self, icon):
|
def setIcon(self, icon):
|
||||||
self._icon = icon
|
self._icon = icon
|
||||||
self.NewIcon.emit()
|
self.dbus_api.NewIcon()
|
||||||
|
|
||||||
def icon(self):
|
def icon(self):
|
||||||
return self._icon
|
return self._icon
|
||||||
@ -182,15 +98,12 @@ class StatusNotifierItemAPI(Object):
|
|||||||
bus = kw['bus'] = dbus.SessionBus()
|
bus = kw['bus'] = dbus.SessionBus()
|
||||||
self.name = '%s-%s-%s' % (self.IFACE, os.getpid(), kw.get('num', 1))
|
self.name = '%s-%s-%s' % (self.IFACE, os.getpid(), kw.get('num', 1))
|
||||||
self.dbus_name = BusName(self.name, bus=bus, do_not_queue=True)
|
self.dbus_name = BusName(self.name, bus=bus, do_not_queue=True)
|
||||||
self.app_id = kw.get('app_id', QApplication.instance().applicationName()) or 'unknown_application'
|
self.app_id = kw.get('app_id') or QApplication.instance().applicationName() or 'unknown_application'
|
||||||
self.category = kw.get('category', 'ApplicationStatus')
|
self.category = kw.get('category') or 'ApplicationStatus'
|
||||||
self.title = kw.get('title', 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)
|
self.dbus_menu = DBusMenu('/StatusItemMenu', **kw)
|
||||||
for name, val in vars(self.__class__).iteritems():
|
|
||||||
if getattr(val, '_dbus_is_signal', False):
|
|
||||||
getattr(notifier, name).connect(getattr(self, name))
|
|
||||||
|
|
||||||
def publish_new_menu(self):
|
def publish_new_menu(self):
|
||||||
menu = self.notifier.contextMenu()
|
menu = self.notifier.contextMenu()
|
||||||
@ -305,24 +218,3 @@ class StatusNotifierItemAPI(Object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
_factory = None
|
|
||||||
def factory():
|
|
||||||
global _factory
|
|
||||||
if _factory is None:
|
|
||||||
_factory = Factory()
|
|
||||||
return _factory
|
|
||||||
|
|
||||||
def test():
|
|
||||||
setup_for_cli_run()
|
|
||||||
app = QApplication([])
|
|
||||||
app.setApplicationName('Testing SNI Interface')
|
|
||||||
tray_icon = factory().create_indicator()
|
|
||||||
tray_icon.setToolTip('A test tooltip')
|
|
||||||
tray_icon.setIcon(QIcon(I('debug.png')))
|
|
||||||
m = QMenu()
|
|
||||||
m.addAction('Quit this application', app.quit)
|
|
||||||
tray_icon.setContextMenu(m)
|
|
||||||
app.exec_()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
test()
|
|
||||||
|
@ -8,9 +8,12 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
import time, sys
|
import time, sys
|
||||||
|
|
||||||
from PyQt5.Qt import QObject, QMenuBar, QAction, QEvent
|
from PyQt5.Qt import QObject, QMenuBar, QAction, QEvent, QSystemTrayIcon
|
||||||
|
|
||||||
|
from calibre.constants import iswindows, isosx
|
||||||
|
|
||||||
UNITY_WINDOW_REGISTRAR = ('com.canonical.AppMenu.Registrar', '/com/canonical/AppMenu/Registrar', 'com.canonical.AppMenu.Registrar')
|
UNITY_WINDOW_REGISTRAR = ('com.canonical.AppMenu.Registrar', '/com/canonical/AppMenu/Registrar', 'com.canonical.AppMenu.Registrar')
|
||||||
|
STATUS_NOTIFIER = ("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher")
|
||||||
|
|
||||||
def log(*args, **kw):
|
def log(*args, **kw):
|
||||||
kw['file'] = sys.stderr
|
kw['file'] = sys.stderr
|
||||||
@ -81,13 +84,18 @@ class Factory(QObject):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QObject.__init__(self)
|
QObject.__init__(self)
|
||||||
try:
|
if iswindows or isosx:
|
||||||
import dbus
|
|
||||||
self.dbus = dbus
|
|
||||||
except ImportError:
|
|
||||||
self.dbus = None
|
self.dbus = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
import dbus
|
||||||
|
self.dbus = dbus
|
||||||
|
except ImportError as err:
|
||||||
|
log('Failed to import dbus, with error:', str(err))
|
||||||
|
self.dbus = None
|
||||||
|
|
||||||
self.menu_registrar = None
|
self.menu_registrar = None
|
||||||
|
self.status_notifier = None
|
||||||
self._bus = None
|
self._bus = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -119,11 +127,41 @@ class Factory(QObject):
|
|||||||
if self.bus.name_has_owner(UNITY_WINDOW_REGISTRAR[0]):
|
if self.bus.name_has_owner(UNITY_WINDOW_REGISTRAR[0]):
|
||||||
self.menu_registrar = UNITY_WINDOW_REGISTRAR
|
self.menu_registrar = UNITY_WINDOW_REGISTRAR
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_status_notifier(self):
|
||||||
|
if self.status_notifier is None:
|
||||||
|
if self.dbus is None:
|
||||||
|
self.status_notifier = False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.detect_status_notifier()
|
||||||
|
except Exception as err:
|
||||||
|
self.status_notifier = False
|
||||||
|
log('Failed to detect window status notifier, with error:', str(err))
|
||||||
|
return bool(self.status_notifier)
|
||||||
|
|
||||||
|
def detect_status_notifier(self):
|
||||||
|
'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'))
|
||||||
|
self.status_notifier = bool(self.bus.call_blocking(*args, timeout=0.1))
|
||||||
|
|
||||||
def create_window_menubar(self, parent):
|
def create_window_menubar(self, parent):
|
||||||
if self.has_global_menu:
|
if self.has_global_menu:
|
||||||
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):
|
||||||
|
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)
|
||||||
|
return ans
|
||||||
|
if iswindows or isosx:
|
||||||
|
return QSystemTrayIcon(parent)
|
||||||
|
|
||||||
def bus_disconnected(self):
|
def bus_disconnected(self):
|
||||||
self._bus = None
|
self._bus = None
|
||||||
for i in xrange(5):
|
for i in xrange(5):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user