From 065017ba25b2c0e802ef74ab76c5cd181699ab99 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 31 Oct 2014 19:20:53 +0530 Subject: [PATCH] Notes on GmenuModel based DBus menus --- src/calibre/gui2/dbus_export/gtk.py | 28 +++++++++++- src/calibre/gui2/dbus_export/menu2.py | 64 +++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/calibre/gui2/dbus_export/menu2.py diff --git a/src/calibre/gui2/dbus_export/gtk.py b/src/calibre/gui2/dbus_export/gtk.py index da82d6a0a6..4f66ccaae7 100644 --- a/src/calibre/gui2/dbus_export/gtk.py +++ b/src/calibre/gui2/dbus_export/gtk.py @@ -9,7 +9,7 @@ __copyright__ = '2014, Kovid Goyal ' # Demo program to explore the GTK DBus interface, which is only partially documented # at https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI -import sys, dbus, struct +import sys, dbus, struct, time, signal from threading import Thread from pprint import pformat @@ -37,6 +37,11 @@ UI_INFO = """ + + + + + @@ -93,6 +98,12 @@ class MenuExampleWindow(Gtk.ApplicationWindow): self.popup = uimanager.get_widget("/PopupMenu") self.add(box) + i = Gtk.Image.new_from_stock(Gtk.STOCK_OK, Gtk.IconSize.MENU) + # Currently the menu items image is not exported over DBus, so probably + # best to stick with using dbusmenu + uimanager.get_widget('/MenuBar/ChoicesMenu/IconAction') + uimanager.get_widget('/MenuBar/ChoicesMenu/IconAction').set_image(i) + uimanager.get_widget('/MenuBar/ChoicesMenu/IconAction').set_always_show_image(True) def add_file_menu_actions(self, action_group): action_filemenu = Gtk.Action("FileMenu", "File", None, None) @@ -140,6 +151,15 @@ class MenuExampleWindow(Gtk.ApplicationWindow): three = Gtk.ToggleAction("ChoiceThree", "Three", None, None) three.connect("toggled", self.on_menu_choices_toggled) action_group.add_action(three) + ad = Gtk.Action('DisabledAction', 'Disabled Action', None, None) + ad.set_sensitive(False) + action_group.add_action(ad) + ia = Gtk.Action('InvisibleAction', 'Invisible Action', None, None) + ia.set_visible(False) + action_group.add_action(ia) + ta = Gtk.Action('TooltipAction', 'Tooltip Action', 'A tooltip', None) + action_group.add_action(ta) + action_group.add_action(Gtk.Action('IconAction', 'Icon Action', None, None)) def create_ui_manager(self): uimanager = Gtk.UIManager() @@ -263,6 +283,7 @@ class MyApplication(Gtk.Application): def print_dbus_data(self): bus = dbus.SessionBus() + time.sleep(0.5) self.data = [] self.get_actions_description(bus) self.print_menu_start(bus) @@ -273,7 +294,9 @@ class MyApplication(Gtk.Application): print = self.print print('\nActions description') self.actions_desc = d = {} - for name, data in bus.call_blocking(self.bus_name, self.object_path, 'org.gtk.Actions', 'DescribeAll', '', ()).iteritems(): + adata = bus.call_blocking(self.bus_name, self.object_path, 'org.gtk.Actions', 'DescribeAll', '', ()) + for name in sorted(adata): + data = adata[name] d[name] = {'enabled':convert(data[0]), 'param type': convert(data[1]), 'state':convert(data[2])} print ('Name:', name) print (pformat(d[name])) @@ -282,4 +305,5 @@ class MyApplication(Gtk.Application): Gtk.Application.do_startup(self) app = MyApplication(application_id='com.calibre-ebook.test-gtk') +signal.signal(signal.SIGINT, signal.SIG_DFL) sys.exit(app.run(sys.argv)) diff --git a/src/calibre/gui2/dbus_export/menu2.py b/src/calibre/gui2/dbus_export/menu2.py new file mode 100644 index 0000000000..2d69f1d6bf --- /dev/null +++ b/src/calibre/gui2/dbus_export/menu2.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2014, Kovid Goyal ' + +# A implementation of the GMenuModel export of menus/actions on DBus. +# GMenuModel is pretty bad, does not support icons, for instance, so I have not +# bothered to complete it. See gtk.py for an exmaple app that creates a +# GMenuModel menu. +# +# Partial spec: https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI + +import dbus +from PyQt5.Qt import QObject, pyqtSignal, QTimer, Qt + +from calibre.utils.dbus_service import Object, method as dbus_method, signal as dbus_signal +from calibre.gui2.dbus_export.utils import set_X_window_properties + +def add_window_properties_for_menu(widget, object_path, bus): + op = unicode(object_path) + set_X_window_properties(widget.effectiveWinId(), _UNITY_OBJECT_PATH=op, _GTK_UNIQUE_BUS_NAME=unicode(bus.get_unique_name()), _GTK_MENUBAR_OBJECT_PATH=op) + + +class DBusMenu(QObject): + + handle_event_signal = pyqtSignal(object, object, object, object) + + 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, bus=bus) + self.set_status = self.dbus_api.set_status + self._next_id = 0 + self.action_changed_timer = t = QTimer(self) + t.setInterval(0), t.setSingleShot(True), t.timeout.connect(self.actions_changed) + self.layout_changed_timer = t = QTimer(self) + 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 + + +class DBusMenuAPI(Object): + + ACTIONS_IFACE = 'org.gtk.Actions' + + def __init__(self, menu, object_path, bus=None): + if bus is None: + bus = dbus.SessionBus() + Object.__init__(self, bus, object_path) + self.status = 'normal' + self.menu = menu + self.revision = 0 + + dbus_method, dbus_signal + +