mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-11 09:13:57 -04:00
Skeleton of the DBus API for dbusmenu
This commit is contained in:
parent
4cff896ed4
commit
67ba10d3bf
@ -6,22 +6,111 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import dbus
|
# Support for excporting Qt's MenuBars/Menus over DBUS. The API is defined in
|
||||||
|
# dbus-menu.xml from the libdbusmenu project https://launchpad.net/libdbusmenu
|
||||||
|
|
||||||
from calibre.utils.dbus_service import Object
|
import dbus
|
||||||
|
from PyQt5.Qt import QApplication, QMenu, QIcon, QKeySequence
|
||||||
|
|
||||||
|
from calibre.utils.dbus_service import Object, BusName, method as dbus_method, dbus_property, signal as dbus_signal
|
||||||
|
from calibre.gui2.dbus_export.utils import setup_for_cli_run
|
||||||
|
|
||||||
class DBusMenu(Object):
|
class DBusMenu(Object):
|
||||||
|
|
||||||
IFACE = 'com.canonical.dbusmenu'
|
IFACE = 'com.canonical.dbusmenu'
|
||||||
|
|
||||||
def __init__(self, notifier, object_path, **kw):
|
def __init__(self, object_path, **kw):
|
||||||
self.notifier = notifier
|
|
||||||
bus = kw.get('bus')
|
bus = kw.get('bus')
|
||||||
if bus is None:
|
if bus is None:
|
||||||
bus = kw['bus'] = dbus.SessionBus()
|
bus = kw['bus'] = dbus.SessionBus()
|
||||||
Object.__init__(self, bus, object_path)
|
Object.__init__(self, bus, object_path)
|
||||||
|
self.status = 'normal'
|
||||||
|
|
||||||
def publish_new_menu(self):
|
def publish_new_menu(self, qmenu):
|
||||||
|
self.qmenu = qmenu
|
||||||
|
|
||||||
|
@dbus_property(IFACE, signature='u')
|
||||||
|
def Version(self):
|
||||||
|
return 3 # GTK 3 uses 3, KDE 4 uses 2
|
||||||
|
|
||||||
|
@dbus_property(IFACE, signature='s', emits_changed_signal=True)
|
||||||
|
def Status(self):
|
||||||
|
return self.status
|
||||||
|
|
||||||
|
def set_status(self, normal=True):
|
||||||
|
self.status = 'normal' if normal else 'notice'
|
||||||
|
self.PropertiesChanged(self.IFACE, {'Status': self.status}, [])
|
||||||
|
|
||||||
|
@dbus_property(IFACE, signature='s')
|
||||||
|
def TextDirection(self):
|
||||||
|
return 'ltr'
|
||||||
|
|
||||||
|
@dbus_property(IFACE, signature='as')
|
||||||
|
def IconThemePath(self):
|
||||||
|
return dbus.Array(signature='s')
|
||||||
|
|
||||||
|
@dbus_method(IFACE, in_signature='iias', out_signature='u(ia{sv}av)')
|
||||||
|
def GetLayout(self, parentId, recursionDepth, propertyNames):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@dbus_method(IFACE, in_signature='aias', out_signature='a(ia{sv})')
|
||||||
|
def GetGroupProperties(self, ids, propertyNames):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus_method(IFACE, in_signature='is', out_signature='v')
|
||||||
|
def GetProperty(self, id, name):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus_method(IFACE, in_signature='isvu', out_signature='')
|
||||||
|
def Event(self, id, eventId, data, timestamp):
|
||||||
|
''' This is called by the applet to notify the application an event happened on a
|
||||||
|
menu item. type can be one of the following::
|
||||||
|
* "clicked"
|
||||||
|
* "hovered"
|
||||||
|
* "opened"
|
||||||
|
* "closed"
|
||||||
|
Vendor specific events can be added by prefixing them with "x-<vendor>-"'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus_method(IFACE, in_signature='a(isvu)', out_signature='ai')
|
||||||
|
def EventGroup(self, events):
|
||||||
|
''' Used to pass a set of events as a single message for possibily
|
||||||
|
several different menuitems. This is done to optimize DBus traffic.
|
||||||
|
Should return a list of ids that are not found. events is a list of
|
||||||
|
events in the same format as used for the Event method.'''
|
||||||
|
return dbus.Array(signature='u')
|
||||||
|
|
||||||
|
@dbus_method(IFACE, in_signature='i', out_signature='b')
|
||||||
|
def AboutToShow(self, id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus_method(IFACE, in_signature='ai', out_signature='aiai')
|
||||||
|
def AboutToShowGroup(self, ids):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus_signal(IFACE, 'a(ia{sv})a(ias)')
|
||||||
|
def ItemsPropertiesUpdated(self, updatedProps, removedProps):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus_signal(IFACE, 'ui')
|
||||||
|
def LayoutUpdated(self, revision, parent):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus_signal(IFACE, 'iu')
|
||||||
|
def ItemActivationRequested(self, id, timestamp):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test():
|
||||||
|
setup_for_cli_run()
|
||||||
|
app = QApplication([])
|
||||||
|
bus = dbus.SessionBus()
|
||||||
|
dbus_name = BusName('com.calibre-ebook.TestDBusMenu', bus=bus, do_not_queue=True)
|
||||||
|
m = QMenu()
|
||||||
|
m.addAction(QIcon(I('window-close.png')), 'Quit', app.quit).setShortcut(QKeySequence(QKeySequence.Quit))
|
||||||
|
menu = DBusMenu('/Menu', bus=bus)
|
||||||
|
menu.publish_new_menu(m)
|
||||||
|
app.exec_()
|
||||||
|
del dbus_name
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
||||||
|
@ -19,7 +19,7 @@ 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
|
from calibre.gui2.dbus_export.utils import log, qicon_to_sni_image_list, setup_for_cli_run
|
||||||
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):
|
class Factory(QObject):
|
||||||
@ -187,13 +187,21 @@ class StatusNotifierItemAPI(Object):
|
|||||||
self.title = kw.get('title', self.app_id)
|
self.title = kw.get('title', 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(notifier, '/StatusItemMenu', **kw)
|
self.dbus_menu = DBusMenu('/StatusItemMenu', **kw)
|
||||||
for name, val in vars(self.__class__).iteritems():
|
for name, val in vars(self.__class__).iteritems():
|
||||||
if getattr(val, '_dbus_is_signal', False):
|
if getattr(val, '_dbus_is_signal', False):
|
||||||
getattr(notifier, name).connect(getattr(self, name))
|
getattr(notifier, name).connect(getattr(self, name))
|
||||||
|
|
||||||
def publish_new_menu(self):
|
def publish_new_menu(self):
|
||||||
self.dbus_menu.publish_new_menu()
|
menu = self.notifier.contextMenu()
|
||||||
|
if menu is None:
|
||||||
|
menu = QMenu()
|
||||||
|
if len(menu.actions()) == 0:
|
||||||
|
menu.addAction(self.notifier.icon(), _('Show/hide %s') % self.title, self.notifier.activated.emit)
|
||||||
|
# The menu must have at least one entry, namely the show/hide entry.
|
||||||
|
# This is necessary as Canonical in their infinite wisdom decided to
|
||||||
|
# force all tray icons to show their popup menus when clicked.
|
||||||
|
self.dbus_menu.publish_new_menu(menu)
|
||||||
|
|
||||||
@dbus_property(IFACE, signature='s')
|
@dbus_property(IFACE, signature='s')
|
||||||
def IconName(self):
|
def IconName(self):
|
||||||
@ -305,13 +313,9 @@ def factory():
|
|||||||
return _factory
|
return _factory
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
import signal
|
setup_for_cli_run()
|
||||||
from dbus.mainloop.glib import DBusGMainLoop, threads_init
|
|
||||||
DBusGMainLoop(set_as_default=True)
|
|
||||||
threads_init()
|
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
app.setApplicationName('Testing SNI Interface')
|
app.setApplicationName('Testing SNI Interface')
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL) # quit on Ctrl-C
|
|
||||||
tray_icon = factory().create_indicator()
|
tray_icon = factory().create_indicator()
|
||||||
tray_icon.setToolTip('A test tooltip')
|
tray_icon.setToolTip('A test tooltip')
|
||||||
tray_icon.setIcon(QIcon(I('debug.png')))
|
tray_icon.setIcon(QIcon(I('debug.png')))
|
||||||
|
@ -38,3 +38,10 @@ def qicon_to_sni_image_list(qicon):
|
|||||||
ans.append((w, h, dbus.ByteArray(data)))
|
ans.append((w, h, dbus.ByteArray(data)))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
def setup_for_cli_run():
|
||||||
|
import signal
|
||||||
|
from dbus.mainloop.glib import DBusGMainLoop, threads_init
|
||||||
|
DBusGMainLoop(set_as_default=True)
|
||||||
|
threads_init()
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL) # quit on Ctrl-C
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user