Esempio n. 1
0
def packagekit_install(pack='ffmpeg'):
    """
    Equivalent of:
     qdbus org.freedesktop.PackageKit /org/freedesktop/PackageKit
           org.freedesktop.PackageKit.Modify.InstallPackageNames
           0 ffmpeg  "show-confirm-search,hide-finished"
    Or:
     qdbus org.freedesktop.PackageKit /org/freedesktop/PackageKit
           org.freedesktop.PackageKit.Query.IsInstalled 0 ffmpeg
    See also (dbus) http://www.freedesktop.org/software/PackageKit/pk-faq.html#session-methods
    Doc: http://blog.fpmurphy.com/2013/11/packagekit-d-bus-abstraction-layer.html
    """
    from PyQt5.QtDBus import QDBusConnection
    from PyQt5.QtDBus import QDBusInterface

    bus = QDBusConnection.sessionBus()
    service_name = 'org.freedesktop.PackageKit'
    service_path = '/org/freedesktop/PackageKit'

    interface = 'org.freedesktop.PackageKit.Query.IsInstalled'
    install = QDBusInterface(service_name, service_path, interface, bus)
    reply = install.call(0, pack, 'show-confirm-search,hide-finished')
    print(reply.arguments())

    interface = 'org.freedesktop.PackageKit.Modify.InstallPackageNames'
    install = QDBusInterface(service_name, service_path, interface, bus)
    reply = install.call(0, pack, 'show-confirm-search,hide-finished')
    print(reply.arguments())
Esempio n. 2
0
def _dbus_notify(title, message, duration=5000):
    from PyQt5.QtDBus import (
        QDBus, QDBusArgument, QDBusConnection, QDBusInterface)
    bus = QDBusConnection.sessionBus()
    if not bus.isConnected():
        raise OSError("Could not connect to DBus")
    interface = QDBusInterface(
        'org.freedesktop.Notifications',
        '/org/freedesktop/Notifications',
        'org.freedesktop.Notifications',
        bus)
    error = interface.lastError()
    if error.type():
        raise RuntimeError("{}; {}".format(error.name(), error.message()))
    # See https://developer.gnome.org/notification-spec/
    # "This allows clients to effectively modify the notification while
    # it's active. A value of value of 0 means that this notification
    # won't replace any existing notifications."
    replaces_id = QVariant(0)
    replaces_id.convert(QVariant.UInt)
    interface.call(
        QDBus.NoBlock,
        'Notify',
        APP_NAME,
        replaces_id,
        resource(settings['application']['tray_icon']),
        title,
        message,
        QDBusArgument([], QMetaType.QStringList),
        {},
        duration)
Esempio n. 3
0
    def __init__(self, qobject):
        '''
            Starts the d-bus service to watch for any stray notifications
        '''
        super().__init__(qobject)

        sb = QDBusConnection.sessionBus()
        print("Notifs_Dbus: Registering notif. service…")
        try:
            sb.registerService("org.freedesktop.Notifications")
        except:
            print("Couldn't register freedesktop notifications service!")

        print("Notifs_Dbus: Registering notif. object…")
        try:
            sb.registerObject("/org/freedesktop/Notifications", qobject)
        except:
            print("Couldn't register freedesktop notifications object!")

        # match string
        m = "eavesdrop='true', interface='org.freedesktop.Notifications', member='Notify', type='method_call'"
        i = QDBusInterface("org.freedesktop.DBus", "/org/freedesktop/DBus",
                           "org.freedesktop.DBus")
        i.call("AddMatch", m)

        print("Notifs_Dbus: Listener should start by now.")
Esempio n. 4
0
def packagekit_install(pack="ffmpeg"):
    """
    Equivalent of:
     qdbus org.freedesktop.PackageKit /org/freedesktop/PackageKit
           org.freedesktop.PackageKit.Modify.InstallPackageNames
           0 ffmpeg  "show-confirm-search,hide-finished"
    Or:
     qdbus org.freedesktop.PackageKit /org/freedesktop/PackageKit
           org.freedesktop.PackageKit.Query.IsInstalled 0 ffmpeg
    See also (dbus) http://www.freedesktop.org/software/PackageKit/pk-faq.html#session-methods
    Doc: http://blog.fpmurphy.com/2013/11/packagekit-d-bus-abstraction-layer.html
    """
    from PyQt5.QtDBus import QDBusConnection
    from PyQt5.QtDBus import QDBusInterface

    bus = QDBusConnection.sessionBus()
    service_name = "org.freedesktop.PackageKit"
    service_path = "/org/freedesktop/PackageKit"

    interface = "org.freedesktop.PackageKit.Query.IsInstalled"
    install = QDBusInterface(service_name, service_path, interface, bus)
    reply = install.call(0, pack, "show-confirm-search,hide-finished")
    print(reply.arguments())

    interface = "org.freedesktop.PackageKit.Modify.InstallPackageNames"
    install = QDBusInterface(service_name, service_path, interface, bus)
    reply = install.call(0, pack, "show-confirm-search,hide-finished")
    print(reply.arguments())
Esempio n. 5
0
class PowerActionManager(QObject):
    def __init__(self, parent = None):
        # manages power actions, and act them.
        super().__init__(parent)
        self._conn = QDBusConnection("Xware Desktop").systemBus()
        self._interface = QDBusInterface(_DBUS_POWER_SERVICE,
                                         _DBUS_POWER_PATH,
                                         _DBUS_POWER_INTERFACE,
                                         self._conn)

        self._actions = {}  # {Action: ActionType}
        self._loadActions()

    def _loadActions(self):
        for action in Action:
            # Always allow Null action
            if action == Action.Null:
                self._actions[action] = ActionType.Special
                continue

            # check if cmd is set
            internalName = action.name
            if app.settings.has("scheduler", internalName + "cmd"):
                self._actions[action] = ActionType.Command
                continue

            # check if logind supports it
            msg = self._interface.call("Can" + internalName)
            if msg.errorName():
                logging.error(msg.errorMessage())
            availability = msg.arguments()[0]
            if availability == "yes":
                self._actions[action] = ActionType.DBus
                continue

    @property
    def actions(self) -> "listlike of actions":
        return self._actions.keys()

    def act(self, action: Action):
        assert isinstance(action, Action), "{} is not an Action".format(action)
        assert action in self._actions, "{} is not available!".format(action)
        actionType = self._actions.get(action, None)
        internalName = action.name
        if actionType is None:
            raise ValueError("{} are not supported!".format(action))
        if actionType == ActionType.Special:
            raise ValueError("Cannot act on {}".format(action))
        elif actionType == ActionType.Command:
            cmd = app.settings.get("scheduler", internalName + "cmd")
            os.system(cmd)
            return
        elif actionType == ActionType.DBus:
            msg = self._interface.call(internalName, False)
            if msg.errorName():
                logging.error(msg.errorMessage())
            return
        else:
            raise ValueError("Unhandled {}".format(action))
Esempio n. 6
0
class KdeSystemTrayIcon(QObject):
    activated = pyqtSignal(QSystemTrayIcon.ActivationReason)

    def __init__(self, application):
        super().__init__(application)
        self.__sessionService = application.sessionService
        self.__settings = application.settings

        self._menubarAdapter = CanonicalDBusMenuAdapter(
            sessionService=self.__sessionService,
            settings=self.__settings,
            app=application,
            parent=self,
        )
        self.__sessionService.registerObject(
            "/MenuBar",
            self._menubarAdapter,
        )

        self._sniAdapter = KdeStatusNotifierAdapter(
            sessionService=self.__sessionService,
            parent=self,
        )
        self.__sessionService.registerObject(
            "/StatusNotifierItem",
            self._sniAdapter,
        )

        self._interface = QDBusInterface(
            "org.kde.StatusNotifierWatcher",
            "/StatusNotifierWatcher",
            "org.kde.StatusNotifierWatcher",
        )

    # Implement necessary interface to mimic QSystemTrayIcon
    def setIcon(self, qIcon: QIcon):
        # extract QIcon's name
        iconName = qIcon.name()
        if not iconName:
            raise ValueError("only support theme icon.")
        self._sniAdapter.setIconName(iconName)

    def setContextMenu(self, qMenu: QMenu):
        pass

    def setVisible(self, visible: bool):
        if visible:
            self._interface.call(
                "RegisterStatusNotifierItem",
                QDBusArgument(self.__sessionService.serviceName,
                              QMetaType.QString),
            )
        else:
            # Maybe try unregistering the whole object?
            raise NotImplementedError(
                "UnregisterStatusNotifierItem method doesn't exist.")
Esempio n. 7
0
class PowerActionManager(QObject):
    def __init__(self, parent=None):
        # manages power actions, and act them.
        super().__init__(parent)
        self._conn = QDBusConnection("Xware Desktop").systemBus()
        self._interface = QDBusInterface(_DBUS_POWER_SERVICE, _DBUS_POWER_PATH, _DBUS_POWER_INTERFACE, self._conn)

        self._actions = {}  # {Action: ActionType}
        self._loadActions()

    def _loadActions(self):
        for action in Action:
            # Always allow Null action
            if action == Action.Null:
                self._actions[action] = ActionType.Special
                continue

            # check if cmd is set
            internalName = action.name
            if app.settings.has("scheduler", internalName + "cmd"):
                self._actions[action] = ActionType.Command
                continue

            # check if logind supports it
            msg = self._interface.call("Can" + internalName)
            if msg.errorName():
                logging.error(msg.errorMessage())
            availability = msg.arguments()[0]
            if availability == "yes":
                self._actions[action] = ActionType.DBus
                continue

    @property
    def actions(self) -> "listlike of actions":
        return self._actions.keys()

    def act(self, action: Action):
        assert isinstance(action, Action), "{} is not an Action".format(action)
        assert action in self._actions, "{} is not available!".format(action)
        actionType = self._actions.get(action, None)
        internalName = action.name
        if actionType is None:
            raise ValueError("{} are not supported!".format(action))
        if actionType == ActionType.Special:
            raise ValueError("Cannot act on {}".format(action))
        elif actionType == ActionType.Command:
            cmd = app.settings.get("scheduler", internalName + "cmd")
            os.system(cmd)
            return
        elif actionType == ActionType.DBus:
            msg = self._interface.call(internalName, False)
            if msg.errorName():
                logging.error(msg.errorMessage())
            return
        else:
            raise ValueError("Unhandled {}".format(action))
 def __init__(self, dbus_name, object_name):
     QObject.__init__(self)
     
     iface = QDBusInterface(dbus_name, object_name, '', QDBusConnection.sessionBus())
     if iface.isValid():
         iface.call("unique")
         sys.exit(1)
     
     QDBusConnection.sessionBus().registerService(dbus_name)
     QDBusConnection.sessionBus().registerObject(object_name, self, QDBusConnection.ExportAllSlots)
Esempio n. 9
0
 def __init__(self, dbus_name, object_name):
     QObject.__init__(self)
     
     iface = QDBusInterface(dbus_name, object_name, '', QDBusConnection.sessionBus())
     if iface.isValid():
         iface.call("unique")
         sys.exit(1)
     
     QDBusConnection.sessionBus().registerService(dbus_name)
     QDBusConnection.sessionBus().registerObject(object_name, self, QDBusConnection.ExportAllSlots)
 def __grabSelectedWindow(self):
     """
     Private method to grab a selected window.
     """
     snapshot = QPixmap()
     
     if Globals.isKdeDesktop():
         mask = 0
         if self.__captureDecorations:
             mask |= 1
         if self.__captureCursor:
             mask |= 2
         interface = QDBusInterface(
             "org.kde.KWin",
             "/Screenshot",
             "org.kde.kwin.Screenshot"
         )
         reply = interface.call(
             "interactive",
             mask
         )
         if self.__checkReply(reply, 1):
             filename = reply.arguments()[0]
             if filename:
                 snapshot = QPixmap(filename)
                 try:
                     os.remove(filename)
                 except OSError:
                     # just ignore it
                     pass
     elif Globals.isGnomeDesktop():
         path = self.__temporaryFilename()
         interface = QDBusInterface(
             "org.gnome.Shell",
             "/org/gnome/Shell/Screenshot",
             "org.gnome.Shell.Screenshot"
         )
         reply = interface.call(
             "ScreenshotWindow",
             self.__captureDecorations,
             self.__captureCursor,
             False,
             path
         )
         if self.__checkReply(reply, 2):
             filename = reply.arguments()[1]
             if filename:
                 snapshot = QPixmap(filename)
                 try:
                     os.remove(filename)
                 except OSError:
                     # just ignore it
                     pass
     
     self.grabbed.emit(snapshot)
Esempio n. 11
0
    def uninhibitScreenlock(self):
        if not QDBusConnection.sessionBus().isConnected():
            sys.stderr.write("Cannot connect to the D-Bus session bus.\n"
                             "To start it, run:\n"
                             "\teval `dbus-launch --auto-syntax`\n")
            return

        iface = QDBusInterface('org.kde.screensaver', '/ScreenSaver', '',
                               QDBusConnection.sessionBus())

        if iface.isValid():
            iface.call('UnInhibit', self.screenLockCookie)
Esempio n. 12
0
class KdeSystemTrayIcon(QObject):
    activated = pyqtSignal(QSystemTrayIcon.ActivationReason)

    def __init__(self, application):
        super().__init__(application)
        self.__sessionService = application.sessionService
        self.__settings = application.settings

        self._menubarAdapter = CanonicalDBusMenuAdapter(
            sessionService = self.__sessionService,
            settings = self.__settings,
            app = application,
            parent = self,
        )
        self.__sessionService.registerObject(
            "/MenuBar", self._menubarAdapter,
        )

        self._sniAdapter = KdeStatusNotifierAdapter(
            sessionService = self.__sessionService,
            parent = self,
        )
        self.__sessionService.registerObject(
            "/StatusNotifierItem", self._sniAdapter,
        )

        self._interface = QDBusInterface(
            "org.kde.StatusNotifierWatcher",
            "/StatusNotifierWatcher",
            "org.kde.StatusNotifierWatcher",
        )

    # Implement necessary interface to mimic QSystemTrayIcon
    def setIcon(self, qIcon: QIcon):
        # extract QIcon's name
        iconName = qIcon.name()
        if not iconName:
            raise ValueError("only support theme icon.")
        self._sniAdapter.setIconName(iconName)

    def setContextMenu(self, qMenu: QMenu):
        pass

    def setVisible(self, visible: bool):
        if visible:
            self._interface.call(
                "RegisterStatusNotifierItem",
                QDBusArgument(self.__sessionService.serviceName, QMetaType.QString),
            )
        else:
            # Maybe try unregistering the whole object?
            raise NotImplementedError("UnregisterStatusNotifierItem method doesn't exist.")
 def __grabFullscreen(self):
     """
     Private method to grab the complete desktop.
     """
     snapshot = QPixmap()
     
     if Globals.isKdeDesktop():
         interface = QDBusInterface(
             "org.kde.KWin",
             "/Screenshot",
             "org.kde.kwin.Screenshot"
         )
         reply = interface.call(
             "screenshotFullscreen",
             self.__captureCursor
         )
         if self.__checkReply(reply, 1):
             filename = reply.arguments()[0]
             if filename:
                 snapshot = QPixmap(filename)
                 try:
                     os.remove(filename)
                 except OSError:
                     # just ignore it
                     pass
     elif Globals.isGnomeDesktop():
         path = self.__temporaryFilename()
         interface = QDBusInterface(
             "org.gnome.Shell",
             "/org/gnome/Shell/Screenshot",
             "org.gnome.Shell.Screenshot"
         )
         reply = interface.call(
             "Screenshot",
             self.__captureCursor,
             False,
             path
         )
         if self.__checkReply(reply, 2):
             filename = reply.arguments()[1]
             if filename:
                 snapshot = QPixmap(filename)
                 try:
                     os.remove(filename)
                 except OSError:
                     # just ignore it
                     pass
     
     self.grabbed.emit(snapshot)
Esempio n. 14
0
def main():
    import sys

    app = QCoreApplication(sys.argv)  # noqa: F841

    if not QDBusConnection.sessionBus().isConnected():
        sys.stderr.write("Cannot connect to the D-Bus session bus.\n"
                         "To start it, run:\n"
                         "\teval `dbus-launch --auto-syntax`\n")
        sys.exit(1)

    iface = QDBusInterface(SERVICE_NAME, "/", "", QDBusConnection.sessionBus())

    if iface.isValid():
        msg = iface.call("ping", sys.argv[1] if len(sys.argv) > 1 else "")
        reply = QDBusReply(msg)

        if reply.isValid():
            sys.stdout.write("Reply was: %s\n" % reply.value())
            sys.exit()

        sys.stderr.write("Call failed: %s\n" % reply.error().message())
        sys.exit(1)

    sys.stderr.write("%s\n" %
                     QDBusConnection.sessionBus().lastError().message())
    sys.exit(1)
Esempio n. 15
0
class ClementineDBusInterface(object):
    application = None

    session_bus_connection = None
    dbus_message_handler = None

    player_interface = None
    root_interface = None
    tracklist_interface = None

    def _on_message(self, msg):
        if msg.arguments()[0]:
            self.application.metadata.update(msg.arguments()[0])
            self.application.render_template()

    def __init__(self, application):
        self.application = application

        service_name = 'org.mpris.MediaPlayer2.clementine'
        service_path = '/Player'
        interface_name = 'org.freedesktop.MediaPlayer'
        signal_name = 'TrackChange'

        self.session_bus_connection = QDBusConnection.sessionBus()

        self.player = QDBusInterface(
            service_name, service_path, interface_name,
            self.session_bus_connection)
        self._on_message(self.player.call('GetMetadata'))

        self.dbus_message_handler = DBusMsgHandler(self._on_message)
        self.session_bus_connection.connect(
            None, None, interface_name, signal_name,
            self.dbus_message_handler.handle)
Esempio n. 16
0
    def __init__(self, dbus_name, object_name):
        QObject.__init__(self)

        search_option = len(sys.argv) >= 2 and sys.argv[1] == "--search"
        iface = QDBusInterface(dbus_name, object_name, '', QDBusConnection.sessionBus())
        
        if iface.isValid():
            iface.call("unique")

            if search_option:
                iface.call("search")
            
            sys.exit(1)
        
        QDBusConnection.sessionBus().registerService(dbus_name)
        QDBusConnection.sessionBus().registerObject(object_name, self, QDBusConnection.ExportAllSlots)
Esempio n. 17
0
class DBusService(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.sessionBus = QDBusConnection.sessionBus()
        self._dbusInterface = QDBusInterface("org.freedesktop.DBus", "/",
                                             "org.freedesktop.DBus")

        if self.serviceExists(self.serviceName):
            raise RuntimeError("There's a DBus that has the same name.")

        created = self.sessionBus.registerService(self.serviceName)
        if not created:
            raise RuntimeError("Cannot create DBus Service.")
        self.registerObject("/", self)

    def registerObject(self, path: str, adapter: QObject):
        return self.sessionBus.registerObject(
            path,
            adapter,
            QDBusConnection.ExportAllSlots
            | QDBusConnection.ExportAllProperties
            | QDBusConnection.ExportAllSignals,
        )

    @property
    def serviceName(self):
        return constants.DBUS_SESSION_SERVICE

    def serviceExists(self, name) -> bool:
        msg = self._dbusInterface.call("ListNames", )
        return name in msg.arguments()[0]
Esempio n. 18
0
def unmount_usb_device(block_device: str):
    """
    Attempts to unmount a USB device via org.freedesktop.UDisks2.Filesystem D-Bus interface as described at
    http://storaged.org/doc/udisks2-api/latest/gdbus-org.freedesktop.UDisks2.Filesystem.html#gdbus-method-org-freedesktop-UDisks2-Filesystem.Unmount.

    :param block_device: a partition name to unmount, for example /dev/sdb1
    """
    if block_device is None:
        raise TypeError("'block_device' cannot be of type 'NoneType'")
    elif block_device == '':
        raise ValueError("'block_device' cannot be empty")

    path = block_device.replace('/dev',
                                '/org/freedesktop/UDisks2/block_devices')
    file_system_interface = QDBusInterface(
        'org.freedesktop.UDisks2', path, 'org.freedesktop.UDisks2.Filesystem',
        QDBusConnection.systemBus())
    if not file_system_interface.isValid():
        raise RuntimeError(_translate('DriveUtils', 'Invalid D-Bus interface'))

    reply = file_system_interface.call('Unmount', {})

    if reply.type() == QDBusMessage.ErrorMessage:
        raise RuntimeError(reply.errorMessage())
    elif reply.type() != QDBusMessage.ReplyMessage:
        raise RuntimeError(
            _translate('DriveUtils', 'Unexpected reply from Udisks'))
Esempio n. 19
0
class DBusService(QObject):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.sessionBus = QDBusConnection.sessionBus()
        self._dbusInterface = QDBusInterface("org.freedesktop.DBus",
                                             "/",
                                             "org.freedesktop.DBus")

        if self.serviceExists(self.serviceName):
            raise RuntimeError("There's a DBus that has the same name.")

        created = self.sessionBus.registerService(self.serviceName)
        if not created:
            raise RuntimeError("Cannot create DBus Service.")
        self.registerObject("/", self)

    def registerObject(self, path: str, adapter: QObject):
        return self.sessionBus.registerObject(
            path,
            adapter,
            QDBusConnection.ExportAllSlots | QDBusConnection.ExportAllProperties |
            QDBusConnection.ExportAllSignals,
        )

    @property
    def serviceName(self):
        return constants.DBUS_SESSION_SERVICE

    def serviceExists(self, name) -> bool:
        msg = self._dbusInterface.call(
            "ListNames",
        )
        return name in msg.arguments()[0]
Esempio n. 20
0
    def __init__(self, dbus_name, object_name):
        QObject.__init__(self)

        search_option = len(sys.argv) >= 2 and sys.argv[1] == "--search"
        iface = QDBusInterface(dbus_name, object_name, '',
                               QDBusConnection.sessionBus())

        if iface.isValid():
            iface.call("unique")

            if search_option:
                iface.call("search")

            sys.exit(1)

        QDBusConnection.sessionBus().registerService(dbus_name)
        QDBusConnection.sessionBus().registerObject(
            object_name, self, QDBusConnection.ExportAllSlots)
Esempio n. 21
0
def method2():
    sys.stdout.write("Method 2:\n")

    bus = QDBusConnection.sessionBus()
    dbus_iface = QDBusInterface('org.freedesktop.DBus',
            '/org/freedesktop/DBus', 'org.freedesktop.DBus', bus)
    names = dbus_iface.call('ListNames').arguments()[0]

    # Mimic the output from the C++ version.
    sys.stdout.write('QVariant(QStringList, ("%s") )\n' % '", "'.join(names))
Esempio n. 22
0
def method2():
    sys.stdout.write("Method 2:\n")

    bus = QDBusConnection.sessionBus()
    dbus_iface = QDBusInterface('org.freedesktop.DBus',
                                '/org/freedesktop/DBus',
                                'org.freedesktop.DBus', bus)
    names = dbus_iface.call('ListNames').arguments()[0]

    # Mimic the output from the C++ version.
    sys.stdout.write('QVariant(QStringList, ("%s") )\n' % '", "'.join(names))
Esempio n. 23
0
    def get_devices(self):
        devices = []

        interface = QDBusInterface(self.DBUS_SERVICE, self.DBUS_PATH,
                                   'org.freedesktop.DBus.Introspectable',
                                   self.bus)
        xml_str = interface.call('Introspect').arguments()[0]
        for child in ElementTree.fromstring(xml_str):
            if child.tag == 'node':
                devices.append(child.attrib['name'])

        return devices
Esempio n. 24
0
    def inhibitScreenlock(self):
        if not QDBusConnection.sessionBus().isConnected():
            sys.stderr.write("Cannot connect to the D-Bus session bus.\n"
                    "To start it, run:\n"
                    "\teval `dbus-launch --auto-syntax`\n");
            return

        iface = QDBusInterface('org.kde.screensaver', '/ScreenSaver', '',
                QDBusConnection.sessionBus())

        if iface.isValid():
            msg = iface.call('Inhibit', 'DisUpgradeViewKDE', 'Upgrading base OS')
            reply = QDBusReply(msg)
            self.screenLockCookie = reply.value()
 def __grabRectangle(self):
     """
     Private method to grab a rectangular desktop area.
     """
     snapshot = QPixmap()
     
     if Globals.isGnomeDesktop():
         # Step 1: let the user select the area
         interface = QDBusInterface(
             "org.gnome.Shell",
             "/org/gnome/Shell/Screenshot",
             "org.gnome.Shell.Screenshot"
         )
         reply = interface.call("SelectArea")
         if self.__checkReply(reply, 4):
             x, y, width, height = reply.arguments()[:4]
             
             # Step 2: grab the selected area
             path = self.__temporaryFilename()
             reply = interface.call(
                 "ScreenshotArea",
                 x, y, width, height,
                 False,
                 path
             )
             if self.__checkReply(reply, 2):
                 filename = reply.arguments()[1]
                 if filename:
                     snapshot = QPixmap(filename)
                     try:
                         os.remove(filename)
                     except OSError:
                         # just ignore it
                         pass
     
     self.grabbed.emit(snapshot)
Esempio n. 26
0
class YoudaoIndicator(QtCore.QObject):

    DBUS_NAME = "com.youdao.indicator"
    DBUS_PATH = "/com/youdao/indicator"
    DBUS_IFACE = "com.youdao.indicator"

    onMenuItemClicked = QtCore.pyqtSignal(str)
    onCheckMenuItemClicked = QtCore.pyqtSignal(str, bool)

    def __init__(self):
        QtCore.QObject.__init__(self)
        self.session_bus = QDBusConnection.sessionBus()
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'MenuItemClicked', self.MenuItemClickedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'CheckMenuItemClicked', self.CheckMenuItemClickedSlot)

        self._iface = QDBusInterface(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE, self.session_bus)

    @QtCore.pyqtSlot(result=bool)
    def isExists(self):
        self._iface.call("Hello")
        return not self._iface.lastError().isValid()

    @QtCore.pyqtSlot(str)
    def MenuItemClickedSlot(self, menu_id):
        self.onMenuItemClicked.emit(menu_id)

    @QtCore.pyqtSlot(str, bool)
    def CheckMenuItemClickedSlot(self, menu_id, enable):
        self.onCheckMenuItemClicked.emit(menu_id, enable)

    @QtCore.pyqtSlot(bool)
    def SetOcrEnable(self, enable):
        self._iface.call("SetOcrEnable", enable)

    @QtCore.pyqtSlot(bool)
    def SetStrokeEnable(self, enable):
        self._iface.call("SetStrokeEnable", enable)

    @QtCore.pyqtSlot()
    def Quit(self):
        self._iface.call("Quit")
Esempio n. 27
0
class PowerActionManager(QObject):
    # manages power actions, and act them.
    _conn = None
    _interface = None
    actions = None

    def __init__(self, parent = None):
        super().__init__(parent)
        self._conn = QDBusConnection("Xware Desktop").systemBus()
        self._interface = QDBusInterface(_DBUS_POWER_SERVICE,
                                         _DBUS_POWER_PATH,
                                         _DBUS_POWER_INTERFACE,
                                         self._conn)

        self.actions = (
            PowerAction(self, ACTION_NONE, "无", "None"),
            PowerAction(self, ACTION_POWEROFF, "关机", "PowerOff"),
            PowerAction(self, ACTION_HYBRIDSLEEP, "混合休眠", "HybridSleep"),
            PowerAction(self, ACTION_HIBERNATE, "休眠", "Hibernate"),
            PowerAction(self, ACTION_SUSPEND, "睡眠", "Suspend"),
        )
        logging.info(self.actions)

    def getActionById(self, actionId):
        return self.actions[actionId]

    def act(self, actionId):
        action = self.getActionById(actionId)
        if action.command:
            return self._cmdAct(action)
        elif action.availability == "yes":
            return self._dbusAct(action)
        raise Exception("Unhandled {}".format(action))

    def _dbusAct(self, action):
        logging.info("scheduler is about to act: {}".format(action))
        msg = self._interface.call(action.internalName,
                                   False)
        if msg.errorName():
            logging.error(msg.errorMessage())

    @staticmethod
    def _cmdAct(action):
        logging.info("scheduler is about to execute: {}".format(action))
        os.system(action.command)
Esempio n. 28
0
class DBusThumbnailCache:

    def __init__(self, bus):
        self.cache = QDBusInterface(
            'org.freedesktop.thumbnails.Cache1',
            '/org/freedesktop/thumbnails/Cache1',
            'org.freedesktop.thumbnails.Cache1',
            bus)

    def _call(self, method, *args):
        msg = self.cache.call(method, *args)
        reply = QDBusReply(msg)
        if not reply.isValid():
            raise Exception("Error on method call '{}': {}: {}".format(
                method,
                reply.error().name(),
                reply.error().message()))
        else:
            return msg.arguments()

    def delete(self, files):
        urls = [url_from_path(f) for f in files]
        self._call("Delete", dbus_as(urls))

    def cleanup(self, files, mtime_threshold=0):
        urls = ["file://" + urllib.parse.quote(os.path.abspath(f)) for f in files]
        self._call("Cleanup", dbus_as(urls), mtime_threshold)

    def copy(self, from_files, to_files):
        from_uris = [url_from_path(f) for f in from_files]
        to_uris = [url_from_path(f) for f in to_files]
        self._call("Copy", dbus_as(from_uris), dbus_as(to_uris))

    def move(self, from_files, to_files):
        from_uris = [url_from_path(f) for f in from_files]
        to_uris = [url_from_path(f) for f in to_files]
        self._call("Move", dbus_as(from_uris), dbus_as(to_uris))
Esempio n. 29
0
class Notifier(QObject):
    def __init__(self, parent):
        super().__init__(parent)
        self._conn = QDBusConnection("Xware Desktop").sessionBus()

        self._interface = QDBusInterface(_DBUS_NOTIFY_SERVICE,
                                         _DBUS_NOTIFY_PATH,
                                         _DBUS_NOTIFY_INTERFACE,
                                         self._conn)

        self._notified = {}  # a dict of notifyId: taskDict
        app.taskModel.taskCompleted.connect(self.notifyTaskCompleted, Qt.DirectConnection)

        self._capabilities = self._getCapabilities()
        if "actions" in self._capabilities:
            successful = self._conn.connect(_DBUS_NOTIFY_SERVICE,
                                            _DBUS_NOTIFY_PATH,
                                            _DBUS_NOTIFY_INTERFACE,
                                            "ActionInvoked", self.slotActionInvoked)
            if not successful:
                logging.error("ActionInvoked connect failed.")

        self._qSound_complete = QSound(":/sound/download-complete.wav", self)

    @property
    def isConnected(self):
        return self._conn.isConnected()

    @pyqtSlot("QObject", result = "void")
    def notifyTaskCompleted(self, taskItem):
        if app.settings.getbool("frontend", "notifybysound"):
            self._qSound_complete.play()

        if not app.settings.getbool("frontend", "popnotifications"):
            return

        self._dbus_notifyCompleted(taskItem)

    def _getCapabilities(self):
        # get libnotify server caps and remember it.
        qdBusMsg = self._interface.call(
            "GetCapabilities"
        )
        if qdBusMsg.errorName():
            logging.error("cannot get org.freedesktop.Notifications.GetCapabilities")
            return []
        else:
            return qdBusMsg.arguments()[0]

    def _dbus_notifyCompleted(self, task: "TaskItem"):
        if "actions" in self._capabilities:
            actions = QDBusArgument(["open", "打开", "viewOneFile", "在文件夹中显示"], QMetaType.QStringList)
        else:
            actions = QDBusArgument([], QMetaType.QStringList)

        qdBusMsg = self._interface.call(
            "Notify",
            QDBusArgument("Xware Desktop", QMetaType.QString),  # app_name
            QDBusArgument(0, QMetaType.UInt),  # replace_id
            QDBusArgument("xware-desktop", QMetaType.QString),  # app_icon
            QDBusArgument("下载完成", QMetaType.QString),  # summary
            QDBusArgument(task.name, QMetaType.QString),  # body
            actions,
            {
                "category": "transfer.complete",
            },  # hints
            QDBusArgument(5000, QMetaType.Int),  # timeout
        )

        if qdBusMsg.errorName():
            logging.error("DBus, notifyTask {}: {}".format(qdBusMsg.errorName(),
                                                           qdBusMsg.errorMessage()))
        else:
            # add it to the dict
            notificationId = qdBusMsg.arguments()[0]
            self._notified[notificationId] = task.id

    @pyqtSlot(QDBusMessage)
    def slotActionInvoked(self, msg):
        notifyId, action = msg.arguments()
        taskId = self._notified.get(notifyId, None)
        if not taskId:
            # other applications' notifications
            return

        taskItem = app.taskModel.taskManager.get(taskId, None)
        if not taskItem:
            logging.debug("taskItem cannot be found anymore in TaskModel.")
            return

        fullpath = taskItem.fullpath  # path + name

        if action == "open":
            return systemOpen(fullpath)
        elif action == "viewOneFile":
            return viewOneFile(fullpath)
        elif action == "default":  # Unity's notify osd always have a default action.
            return
        else:
            raise Exception("Unknown action from slotActionInvoked: {}.".format(action))
Esempio n. 30
0
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtDBus import QDBusConnection, QDBusInterface, QDBusReply

if __name__ == "__main__":
    app = QCoreApplication(sys.argv)

    if not QDBusConnection.sessionBus().isConnected():
        sys.stderr.write("Cannot connect to the D-Bus session bus.\n"
                         "To start it, run:\n"
                         "\teval `dbus-launch --auto-syntax`\n")
        sys.exit(1)

    iface = QDBusInterface("org.example.QtDBus.PingExample", "/", "",
                           QDBusConnection.sessionBus())

    if iface.isValid():
        msg = iface.call("ping", sys.argv[1] if len(sys.argv) > 1 else "")
        reply = QDBusReply(msg)

        if reply.isValid():
            sys.stdout.write("Reply was: %s\n" % reply.value())
            sys.exit()

        sys.stderr.write("Call failed: %s\n" % reply.error().message())
        sys.exit(1)

    sys.stderr.write("%s\n" %
                     QDBusConnection.sessionBus().lastError().message())
    sys.exit(1)
Esempio n. 31
0
class ExternalPartitions(QObject):
	def __init__(self):
		"""
			Get _partitions, a list of things you can save video to.
			{
				"name": "Testdisk",
				"device": "mmcblk0p1",
				"uuid": "a14d610d-b524-4af2-9a1a-fa3dd1184258",
				"path": bytes("/dev/sda", 'utf8'),
				"size": 1294839100, #bytes, 64-bit positive integer
				"readOnly": False,
				"interface": "usb", #"usb" or "sd"
			}
		"""
		super().__init__()
		self._partitions = []
		
		#observers collection
		self._callbacks = []
		self.uDisks2ObjectManager = QDBusInterface(
			f"org.freedesktop.UDisks2", #Service
			f"/org/freedesktop/UDisks2", #Path
			f"org.freedesktop.DBus.ObjectManager", #Interface
			QDBusConnection.systemBus(),
		)
		self.uDisks2ObjectManager.setTimeout(10) #Set to 1000 after startup period.
		
		#Retry. This doesn't connect the first time, no matter what the time limit is. I don't know why, probably something in the start-on-demand logic.
		if not self.uDisks2ObjectManager.isValid():
			self.uDisks2ObjectManager = QDBusInterface(
				f"org.freedesktop.UDisks2", #Service
				f"/org/freedesktop/UDisks2", #Path
				f"org.freedesktop.DBus.ObjectManager", #Interface
				QDBusConnection.systemBus(),
			)
			self.uDisks2ObjectManager.setTimeout(10)
			
			if not self.uDisks2ObjectManager.isValid():
				log.critical(f"Error: Can not connect to udisks2 at {self.uDisks2ObjectManager.service()}. ({self.uDisks2ObjectManager.lastError().name()}: {self.uDisks2ObjectManager.lastError().message()}) Try running `apt install udisks2`?")
				raise Exception("D-Bus Setup Error")
		
		self.uDisks2ObjectManager.setTimeout(1000)
		
		
		#The .connect call freezes if we don't do this, or if we do this twice.
		#This bug was fixed by Qt 5.11.
		QDBusConnection.systemBus().registerObject(
			f"/org/freedesktop/UDisks2", 
			self,
		)
		
		QDBusConnection.systemBus().connect(
			f"org.freedesktop.UDisks2", #Service
			f"/org/freedesktop/UDisks2", #Path
			f"org.freedesktop.DBus.ObjectManager", #Interface
			'InterfacesAdded', #Signal
			self.__interfacesAddedEvent,
		)
		
		QDBusConnection.systemBus().connect(
			f"org.freedesktop.UDisks2", #Service
			f"/org/freedesktop/UDisks2", #Path
			f"org.freedesktop.DBus.ObjectManager", #Interface
			'InterfacesRemoved', #Signal
			self.__interfacesRemovedEvent,
		)	
		
		for name, data in QDBusReply(self.uDisks2ObjectManager.call('GetManagedObjects')).value().items():
			self.__interfacesAdded(name, data)
	
	def __getitem__(self, i):
		return self._partitions[i]
	
	def __repr__(self):
		#pdb uses repr instad of str (which imo is more appropriate for an interactive debugging session)
		return f'{type(self)} ({self._partitions})'
	
	def observe(self, callback):
		"""Add a function to get called when a volume is mounted or unmounted.
			
			The added function is immediately invoked."""
		
		assert callable(callback), f"Callback is not callable. (Expected function, got {callback}.)"
		self._callbacks += [callback]
		callback(self._partitions)
	
	def unobserve(self, callback):
		"""Stop a function from getting called when a volume is mounted or unmounted."""
		
		assert callable(callback), f"Callback is not callable. (Expected function, got {callback}.)"
		self._callbacks = list(filter(
			lambda existingCallback: existingCallback != callback, 
			self._callbacks ) )
	
	
	@pyqtSlot('QDBusMessage')
	def __interfacesAddedEvent(self, msg):
		self.__interfacesAdded(*msg.arguments())
	
	def __interfacesAdded(self, name, data):
		if 'org.freedesktop.UDisks2.Filesystem' in data:
			#"Now, for each file system which just got mounted, …"
			
			#Filter root, which is mounted on / and /media/mmcblk0p2.
			if len(data['org.freedesktop.UDisks2.Filesystem']['MountPoints']) != 1:
				return
			
			#Filter out whatever gets mounted to /boot.
			if not bytes(data['org.freedesktop.UDisks2.Filesystem']['MountPoints'][0]).startswith(b'/media/'):
				return
			
			log.debug(f"Partition mounted at {bytes(data['org.freedesktop.UDisks2.Filesystem']['MountPoints'][0]).decode('utf-8')}.") #toStdString() doesn't seem to exist, perhaps because we don't have std strings.
			
			self._partitions += [{
				'name': data['org.freedesktop.UDisks2.Block']['IdLabel'],
				'device': name,
				'uuid': data['org.freedesktop.UDisks2.Block']['IdUUID'], #Found at `/dev/disk/by-uuid/`.
				'path': bytes(data['org.freedesktop.UDisks2.Filesystem']['MountPoints'][0])[:-1], #Trim off a null byte at the end, we don't need it in python.
				'size': data['org.freedesktop.UDisks2.Block']['Size'], #number of bytes, 64-bit positive integer
				'readOnly': data['org.freedesktop.UDisks2.Block']['ReadOnly'],
				'interface': 'usb' if True in [b'usb' in symlink for symlink in data['org.freedesktop.UDisks2.Block']['Symlinks']] else 'other', #This data comes in one message earlier, but it would be enough complexity to link the two that it makes more sense to just string match here.
			}]
			for callback in self._callbacks:
				callback(self._partitions)
	
	
	@pyqtSlot('QDBusMessage')
	def __interfacesRemovedEvent(self, msg):
		self.__interfacesRemoved(*msg.arguments())
	
	def __interfacesRemoved(self, name, data):
		if 'org.freedesktop.UDisks2.Partition' == data[0]:
			#"Now, for each file system which just got removed, …"
			self._partitions = list(filter(
				lambda partition: partition["device"] != name, 
				self._partitions ) )
			for callback in self._callbacks:
				callback(self._partitions)
	
	
	def list(self):
		return self._partitions
	
	def usageFor(self, device: str, callback: Callable[[], Dict[str,int]]):
		for partition in self._partitions:
			if partition['device'] == device:
				df = subprocess.Popen(
					['df', partition['path'], '--output=avail,used'], #used+avail != 1k-blocks
					stdout=subprocess.PIPE,
					stderr=subprocess.DEVNULL )
				
				def checkDf(*, timeout):
					exitStatus = df.poll()
					if exitStatus is None: #Still running, check again later.
						#Standard clamped exponential decay. Keeps polling to a reasonable amount, frequent at first then low.
						delay(self, timeout, lambda:
							checkDf(timeout=max(1, timeout*2)) )
					elif exitStatus: #df failure, raise an error
						if exitStatus == 1:
							#When a storage device with multiple partitions is removed,
							#the observer fires once for each partition. This means
							#that, for one partition, the client will issue a spurious
							#call to this function with the stale partition's device.
							log.debug(f'Unknown device {device}.')
							log.debug(f'Known devices are {[p["device"] for p in self._partitions]}.')
						else:
							raise Exception(f'df exited with error {exitStatus}')
					else:
						info = ( #Chop up df command output.
							df.communicate()[0]
							.split(b'\n')[1] #Remove first line, column headings
							.split() #Each output is now in a list.
						)
						callback({
							'available': int(info[0]),
							'used': int(info[1]),
							'total': int(info[0]) + int(info[1]),
						})
				delay(self, 0.20, lambda: #Initial delay, df usually runs in .17s.
					checkDf(timeout=0.05) )
Esempio n. 32
0
class Notifier(QObject):
    _conn = None
    _interface = None
    _notifications = None  # a dict of notifyId: taskDict

    _completedTasksStat = None

    def __init__(self, parent):
        super().__init__(parent)
        self._conn = QDBusConnection("Xware Desktop").sessionBus()

        self._interface = QDBusInterface(_DBUS_NOTIFY_SERVICE,
                                         _DBUS_NOTIFY_PATH,
                                         _DBUS_NOTIFY_INTERFACE,
                                         self._conn)

        self._notifications = {}
        self._completedTasksStat = app.etmpy.completedTasksStat
        self._completedTasksStat.sigTaskCompleted.connect(self.notifyTask)

        successful = self._conn.connect(_DBUS_NOTIFY_SERVICE,
                                        _DBUS_NOTIFY_PATH,
                                        _DBUS_NOTIFY_INTERFACE,
                                        "ActionInvoked", self.slotActionInvoked)
        if not successful:
            logging.error("ActionInvoked connect failed.")

        self._qSound_complete = QSound(":/sound/download-complete.wav", self)

    @property
    def isConnected(self):
        return self._conn.isConnected()

    def notifyTask(self, taskId):
        task = self._completedTasksStat.getTask(taskId)

        if task.get("state", None) == 11:  # see definitions in class TaskStatistic.
            if app.settings.getbool("frontend", "notifybysound"):
                self._qSound_complete.play()
            self._dbus_notify(task)
        else:
            # TODO: Also notify if errors occur
            pass

    def _dbus_notify(self, task):
        if not app.settings.getbool("frontend", "popnotifications"):
            return

        qdBusMsg = self._interface.call(
            "Notify",
            QDBusArgument("Xware Desktop", QMetaType.QString),  # app_name
            QDBusArgument(0, QMetaType.UInt),  # replace_id
            QDBusArgument("/opt/xware_desktop/frontend/thunder.ico", QMetaType.QString),  # app_icon
            QDBusArgument("下载完成", QMetaType.QString),  # summary
            QDBusArgument(task["name"], QMetaType.QString),  # body
            QDBusArgument(["open", "打开", "openDir", "打开文件夹"], QMetaType.QStringList),  # actions,
            {
                "category": "transfer.complete",
            },  # hints
            QDBusArgument(5000, QMetaType.Int),  # timeout
        )

        if qdBusMsg.errorName():
            logging.error("DBus, notifyTask {}: {}".format(qdBusMsg.errorName(),
                                                           qdBusMsg.errorMessage()))
        else:
            # add it to the dict
            self._notifications[qdBusMsg.arguments()[0]] = task

    @pyqtSlot(QDBusMessage)
    def slotActionInvoked(self, msg):
        notifyId, action = msg.arguments()
        task = self._notifications.get(notifyId, None)
        if not task:
            # other applications' notifications
            return
        name = task["name"]  # filename
        path = task["path"]  # location

        if action == "open":
            openPath = os.path.join(path, name)
        elif action == "openDir":
            openPath = path
        else:
            raise Exception("Unknown action from slotActionInvoked.")

        nativeOpenPath = app.mountsFaker.convertToNativePath(openPath)
        qUrl = QUrl.fromLocalFile(nativeOpenPath)
        QDesktopServices().openUrl(qUrl)
Esempio n. 33
0
import sys

from PyQt5.QtCore import QCoreApplication
from PyQt5.QtDBus import QDBusConnection, QDBusInterface, QDBusReply


if __name__ == "__main__":
    app = QCoreApplication(sys.argv)

    if not QDBusConnection.sessionBus().isConnected():
        sys.stderr.write(
            "Cannot connect to the D-Bus session bus.\n" "To start it, run:\n" "\teval `dbus-launch --auto-syntax`\n"
        )
        sys.exit(1)

    iface = QDBusInterface("org.example.QtDBus.PingExample", "/", "", QDBusConnection.sessionBus())

    if iface.isValid():
        msg = iface.call("ping", sys.argv[1] if len(sys.argv) > 1 else "")
        reply = QDBusReply(msg)

        if reply.isValid():
            sys.stdout.write("Reply was: %s\n" % reply.value())
            sys.exit()

        sys.stderr.write("Call failed: %s\n" % reply.error().message())
        sys.exit(1)

    sys.stderr.write("%s\n" % QDBusConnection.sessionBus().lastError().message())
    sys.exit(1)
Esempio n. 34
0
class DBusNotificationAdapter(AbstractNotificationAdapter):
    """Send notifications over DBus.

    This is essentially what libnotify does, except using Qt's DBus implementation.

    Related specs:
    https://developer.gnome.org/notification-spec/
    https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html
    https://wiki.ubuntu.com/NotificationDevelopmentGuidelines
    """

    SERVICE = "org.freedesktop.Notifications"
    TEST_SERVICE = "org.qutebrowser.TestNotifications"
    PATH = "/org/freedesktop/Notifications"
    INTERFACE = "org.freedesktop.Notifications"
    SPEC_VERSION = "1.2"  # Released in January 2011, still current in March 2021.
    NAME = "libnotify"

    def __init__(self, parent: QObject = None) -> None:
        super().__init__(parent)
        assert _notifications_supported()

        if utils.is_windows:
            # The QDBusConnection destructor seems to cause error messages (and
            # potentially segfaults) on Windows, so we bail out early in that case.
            # We still try to get a connection on macOS, since it's theoretically
            # possible to run DBus there.
            raise Error("libnotify is not supported on Windows")

        bus = QDBusConnection.sessionBus()
        if not bus.isConnected():
            raise Error("Failed to connect to DBus session bus: " +
                        self._dbus_error_str(bus.lastError()))

        self._watcher = QDBusServiceWatcher(
            self.SERVICE,
            bus,
            QDBusServiceWatcher.WatchForUnregistration,
            self,
        )
        self._watcher.serviceUnregistered.connect(
            self._on_service_unregistered)

        test_service = 'test-notification-service' in objects.debug_flags
        service = f"{self.TEST_SERVICE}{os.getpid()}" if test_service else self.SERVICE

        self.interface = QDBusInterface(service, self.PATH, self.INTERFACE,
                                        bus)
        if not self.interface.isValid():
            raise Error("Could not construct a DBus interface: " +
                        self._dbus_error_str(self.interface.lastError()))

        connections = [
            ("NotificationClosed", self._handle_close),
            ("ActionInvoked", self._handle_action),
        ]
        for name, func in connections:
            if not bus.connect(service, self.PATH, self.INTERFACE, name, func):
                raise Error(f"Could not connect to {name}: " +
                            self._dbus_error_str(bus.lastError()))

        self._quirks = _ServerQuirks()
        if not test_service:
            # Can't figure out how to make this work with the test server...
            # https://www.riverbankcomputing.com/pipermail/pyqt/2021-March/043724.html
            self._get_server_info()

        if self._quirks.skip_capabilities:
            self._capabilities = _ServerCapabilities.from_list([])
        else:
            self._fetch_capabilities()

    @pyqtSlot(str)
    def _on_service_unregistered(self) -> None:
        """Make sure we know when the notification daemon exits.

        If that's the case, we bail out, as otherwise notifications would fail or the
        next start of the server would lead to duplicate notification IDs.
        """
        log.misc.debug("Notification daemon did quit!")
        self.clear_all.emit()

    def _find_quirks(  # noqa: C901 ("too complex"
        self,
        name: str,
        vendor: str,
        ver: str,
    ) -> Optional[_ServerQuirks]:
        """Find quirks to use based on the server information."""
        if (name, vendor) == ("notify-osd", "Canonical Ltd"):
            # Shows a dialog box instead of a notification bubble as soon as a
            # notification has an action (even if only a default one). Dialog boxes are
            # buggy and return a notification with ID 0.
            # https://wiki.ubuntu.com/NotificationDevelopmentGuidelines#Avoiding_actions
            return _ServerQuirks(avoid_actions=True, spec_version="1.1")
        elif (name, vendor) == ("Notification Daemon", "MATE"):
            # Still in active development but doesn't implement spec 1.2:
            # https://github.com/mate-desktop/mate-notification-daemon/issues/132
            quirks = _ServerQuirks(spec_version="1.1")
            if utils.VersionNumber.parse(ver) <= utils.VersionNumber(1, 24):
                # https://github.com/mate-desktop/mate-notification-daemon/issues/118
                quirks.avoid_body_hyperlinks = True
            return quirks
        elif (name, vendor) == ("naughty", "awesome") and ver != "devel":
            # Still in active development but spec 1.0/1.2 support isn't
            # released yet:
            # https://github.com/awesomeWM/awesome/commit/e076bc664e0764a3d3a0164dabd9b58d334355f4
            parsed_version = utils.VersionNumber.parse(ver.lstrip('v'))
            if parsed_version <= utils.VersionNumber(4, 3):
                return _ServerQuirks(spec_version="1.0")
        elif (name, vendor) == ("twmnd", "twmnd"):
            # https://github.com/sboli/twmn/pull/96
            return _ServerQuirks(spec_version="0")
        elif (name, vendor) == ("tiramisu", "Sweets"):
            if utils.VersionNumber.parse(ver) < utils.VersionNumber(2, 0):
                # https://github.com/Sweets/tiramisu/issues/20
                return _ServerQuirks(skip_capabilities=True)
        elif (name, vendor) == ("lxqt-notificationd", "lxqt.org"):
            quirks = _ServerQuirks()
            parsed_version = utils.VersionNumber.parse(ver)
            if parsed_version <= utils.VersionNumber(0, 16):
                # https://github.com/lxqt/lxqt-notificationd/issues/253
                quirks.escape_title = True
            if parsed_version < utils.VersionNumber(0, 16):
                # https://github.com/lxqt/lxqt-notificationd/commit/c23e254a63c39837fb69d5c59c5e2bc91e83df8c
                quirks.icon_key = 'image_data'
            return quirks
        elif (name, vendor) == ("haskell-notification-daemon",
                                "abc"):  # aka "deadd"
            return _ServerQuirks(
                # https://github.com/phuhl/linux_notification_center/issues/160
                spec_version="1.0",
                # https://github.com/phuhl/linux_notification_center/issues/161
                wrong_replaces_id=True,
            )
        elif (name, vendor) == ("ninomiya", "deifactor"):
            return _ServerQuirks(
                no_padded_images=True,
                wrong_replaces_id=True,
            )
        elif (name, vendor) == ("Raven", "Budgie Desktop Developers"):
            # Before refactor
            return _ServerQuirks(
                # https://github.com/solus-project/budgie-desktop/issues/2114
                escape_title=True,
                # https://github.com/solus-project/budgie-desktop/issues/2115
                wrong_replaces_id=True,
            )
        elif (name, vendor) == ("Budgie Notification Server",
                                "Budgie Desktop Developers"):
            # After refactor: https://github.com/BuddiesOfBudgie/budgie-desktop/pull/36
            if utils.VersionNumber.parse(ver) < utils.VersionNumber(10, 6, 2):
                return _ServerQuirks(
                    # https://github.com/BuddiesOfBudgie/budgie-desktop/issues/118
                    wrong_closes_type=True, )
        return None

    def _get_server_info(self) -> None:
        """Query notification server information and set quirks."""
        reply = self.interface.call(QDBus.BlockWithGui, "GetServerInformation")
        self._verify_message(reply, "ssss", QDBusMessage.ReplyMessage)
        name, vendor, ver, spec_version = reply.arguments()

        log.misc.debug(
            f"Connected to notification server: {name} {ver} by {vendor}, "
            f"implementing spec {spec_version}")

        quirks = self._find_quirks(name, vendor, ver)
        if quirks is not None:
            log.misc.debug(f"Enabling quirks {quirks}")
            self._quirks = quirks

        expected_spec_version = self._quirks.spec_version or self.SPEC_VERSION
        if spec_version != expected_spec_version:
            log.misc.warning(
                f"Notification server ({name} {ver} by {vendor}) implements "
                f"spec {spec_version}, but {expected_spec_version} was expected. "
                f"If {name} is up to date, please report a qutebrowser bug.")

        # https://specifications.freedesktop.org/notification-spec/latest/ar01s08.html
        icon_key_overrides = {
            "1.0": "icon_data",
            "1.1": "image_data",
        }
        if spec_version in icon_key_overrides:
            self._quirks.icon_key = icon_key_overrides[spec_version]

    def _dbus_error_str(self, error: QDBusError) -> str:
        """Get a string for a DBus error."""
        if not error.isValid():
            return "Unknown error"
        return f"{error.name()} - {error.message()}"

    def _verify_message(
        self,
        msg: QDBusMessage,
        expected_signature: str,
        expected_type: QDBusMessage.MessageType,
    ) -> None:
        """Check the signature/type of a received message.

        Raises DBusError if the signature doesn't match.
        """
        assert expected_type not in [
            QDBusMessage.ErrorMessage,
            QDBusMessage.InvalidMessage,
        ], expected_type

        if msg.type() == QDBusMessage.ErrorMessage:
            raise DBusError(msg)

        signature = msg.signature()
        if signature != expected_signature:
            raise Error(
                f"Got a message with signature {signature} but expected "
                f"{expected_signature} (args: {msg.arguments()})")

        typ = msg.type()
        if typ != expected_type:
            type_str = debug.qenum_key(QDBusMessage.MessageType, typ)
            expected_type_str = debug.qenum_key(QDBusMessage.MessageType,
                                                expected_type)
            raise Error(
                f"Got a message of type {type_str} but expected {expected_type_str}"
                f"(args: {msg.arguments()})")

    def _verify_notification_id(
        self,
        notification_id: int,
        *,
        replaces_id: int,
    ) -> None:
        """Ensure the returned notification id is valid."""
        if replaces_id not in [0, notification_id]:
            msg = (
                f"Wanted to replace notification {replaces_id} but got new id "
                f"{notification_id}.")
            if self._quirks.wrong_replaces_id:
                log.misc.debug(msg)
            else:
                log.misc.error(msg)

        if notification_id <= 0:
            self.error.emit(f"Got invalid notification id {notification_id}")

    def _get_title_arg(self, title: str) -> str:
        """Get the title argument for present()."""
        # Titles don't support markup (except with broken servers)
        if self._quirks.escape_title:
            return html.escape(title, quote=False)
        return title

    def _get_actions_arg(self) -> QDBusArgument:
        """Get the actions argument for present()."""
        actions = []
        if self._capabilities.actions:
            actions = ['default', 'Activate']  # key, name
        return QDBusArgument(actions, QMetaType.QStringList)

    def _get_hints_arg(self, *, origin_url: QUrl,
                       icon: QImage) -> Dict[str, Any]:
        """Get the hints argument for present()."""
        origin_url_str = origin_url.toDisplayString()
        hints: Dict[str, Any] = {
            # Include the origin in case the user wants to do different things
            # with different origin's notifications.
            "x-qutebrowser-origin": origin_url_str,
            "desktop-entry": "org.qutebrowser.qutebrowser",
        }

        is_useful_origin = self._should_include_origin(origin_url)
        if self._capabilities.kde_origin_name and is_useful_origin:
            hints["x-kde-origin-name"] = origin_url_str

        if icon.isNull():
            filename = 'icons/qutebrowser-64x64.png'
            icon = QImage.fromData(resources.read_file_binary(filename))

        key = self._quirks.icon_key or "image-data"
        data = self._convert_image(icon)
        if data is not None:
            hints[key] = data

        return hints

    def _call_notify_wrapper(
        self,
        *,
        appname: str,
        replaces_id: QVariant,
        icon: str,
        title: str,
        body: str,
        actions: QDBusArgument,
        hints: Dict[str, Any],
        timeout: int,
    ) -> Any:
        """Wrapper around DBus call to use keyword args."""
        return self.interface.call(
            QDBus.BlockWithGui,
            "Notify",
            appname,
            replaces_id,
            icon,
            title,
            body,
            actions,
            hints,
            timeout,
        )

    def present(
        self,
        qt_notification: "QWebEngineNotification",
        *,
        replaces_id: Optional[int],
    ) -> int:
        """Shows a notification over DBus."""
        if replaces_id is None:
            replaces_id = 0  # 0 is never a valid ID according to the spec

        reply = self._call_notify_wrapper(
            appname="qutebrowser",
            replaces_id=_as_uint32(replaces_id),
            icon="",  # we use image-data and friends instead
            title=self._get_title_arg(qt_notification.title()),
            body=self._format_body(
                body=qt_notification.message(),
                origin_url=qt_notification.origin(),
            ),
            actions=self._get_actions_arg(),
            hints=self._get_hints_arg(
                origin_url=qt_notification.origin(),
                icon=qt_notification.icon(),
            ),
            timeout=-1,  # use default
        )

        try:
            self._verify_message(reply, "u", QDBusMessage.ReplyMessage)
        except DBusError as e:
            if e.is_fatal:
                raise
            self.error.emit(e.error_message)
            # Return value gets ignored in NotificationBridgePresenter.present
            return -1

        notification_id = reply.arguments()[0]
        self._verify_notification_id(notification_id, replaces_id=replaces_id)
        return notification_id

    def _convert_image(self, qimage: QImage) -> Optional[QDBusArgument]:
        """Convert a QImage to the structure DBus expects.

        https://specifications.freedesktop.org/notification-spec/latest/ar01s05.html#icons-and-images-formats
        """
        bits_per_color = 8
        has_alpha = qimage.hasAlphaChannel()
        if has_alpha:
            image_format = QImage.Format_RGBA8888
            channel_count = 4
        else:
            image_format = QImage.Format_RGB888
            channel_count = 3

        qimage.convertTo(image_format)
        bytes_per_line = qimage.bytesPerLine()
        width = qimage.width()
        height = qimage.height()

        image_data = QDBusArgument()
        image_data.beginStructure()
        image_data.add(width)
        image_data.add(height)
        image_data.add(bytes_per_line)
        image_data.add(has_alpha)
        image_data.add(bits_per_color)
        image_data.add(channel_count)

        try:
            size = qimage.sizeInBytes()
        except TypeError:
            # WORKAROUND for
            # https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042919.html
            # byteCount() is obsolete, but sizeInBytes() is only available with
            # SIP >= 5.3.0.
            size = qimage.byteCount()

        # Despite the spec not mandating this, many notification daemons mandate that
        # the last scanline does not have any padding bytes.
        #
        # Or in the words of dunst:
        #
        #     The image is serialised rowwise pixel by pixel. The rows are aligned by a
        #     spacer full of garbage. The overall data length of data + garbage is
        #     called the rowstride.
        #
        #     Mind the missing spacer at the last row.
        #
        #     len:     |<--------------rowstride---------------->|
        #     len:     |<-width*pixelstride->|
        #     row 1:   |   data for row 1    | spacer of garbage |
        #     row 2:   |   data for row 2    | spacer of garbage |
        #              |         .           | spacer of garbage |
        #              |         .           | spacer of garbage |
        #              |         .           | spacer of garbage |
        #     row n-1: |   data for row n-1  | spacer of garbage |
        #     row n:   |   data for row n    |
        #
        # Source:
        # https://github.com/dunst-project/dunst/blob/v1.6.1/src/icon.c#L292-L309
        padding = bytes_per_line - width * channel_count
        assert 0 <= padding <= 3, (padding, bytes_per_line, width,
                                   channel_count)
        size -= padding

        if padding and self._quirks.no_padded_images:
            return None

        bits = qimage.constBits().asstring(size)
        image_data.add(QByteArray(bits))

        image_data.endStructure()
        return image_data

    @pyqtSlot(QDBusMessage)
    def _handle_close(self, msg: QDBusMessage) -> None:
        """Handle NotificationClosed from DBus."""
        try:
            self._verify_message(msg, "uu", QDBusMessage.SignalMessage)
        except Error:
            if not self._quirks.wrong_closes_type:
                raise
            self._verify_message(msg, "ui", QDBusMessage.SignalMessage)

        notification_id, _close_reason = msg.arguments()
        self.close_id.emit(notification_id)

    @pyqtSlot(QDBusMessage)
    def _handle_action(self, msg: QDBusMessage) -> None:
        """Handle ActionInvoked from DBus."""
        self._verify_message(msg, "us", QDBusMessage.SignalMessage)
        notification_id, action_key = msg.arguments()
        if action_key == "default":
            self.click_id.emit(notification_id)

    @pyqtSlot(int)
    def on_web_closed(self, notification_id: int) -> None:
        """Send CloseNotification if a notification was closed from JS."""
        self.interface.call(
            QDBus.NoBlock,
            "CloseNotification",
            _as_uint32(notification_id),
        )

    def _fetch_capabilities(self) -> None:
        """Fetch capabilities from the notification server."""
        reply = self.interface.call(
            QDBus.BlockWithGui,
            "GetCapabilities",
        )
        self._verify_message(reply, "as", QDBusMessage.ReplyMessage)

        caplist = reply.arguments()[0]
        self._capabilities = _ServerCapabilities.from_list(caplist)
        if self._quirks.avoid_actions:
            self._capabilities.actions = False
        if self._quirks.avoid_body_hyperlinks:
            self._capabilities.body_hyperlinks = False

        log.misc.debug(
            f"Notification server capabilities: {self._capabilities}")

    def _format_body(self, body: str, origin_url: QUrl) -> str:
        """Format the body according to the server capabilities.

        If the server doesn't support x-kde-origin-name, we include the origin URL as a
        prefix. If possible, we hyperlink it.

        For both prefix and body, we'll need to HTML escape it if the server supports
        body markup.
        """
        urlstr = origin_url.toDisplayString()
        is_useful_origin = self._should_include_origin(origin_url)

        if self._capabilities.kde_origin_name or not is_useful_origin:
            prefix = None
        elif self._capabilities.body_markup and self._capabilities.body_hyperlinks:
            href = html.escape(
                origin_url.toString(
                    QUrl.FullyEncoded)  # type: ignore[arg-type]
            )
            text = html.escape(urlstr, quote=False)
            prefix = f'<a href="{href}">{text}</a>'
        elif self._capabilities.body_markup:
            prefix = html.escape(urlstr, quote=False)
        else:
            prefix = urlstr

        if self._capabilities.body_markup:
            body = html.escape(body, quote=False)

        if prefix is None:
            return body

        return prefix + '\n\n' + body
Esempio n. 35
0
class Notifier(QObject):
    def __init__(self, *, taskModel, frontendSettings, parent):
        super().__init__(parent)
        self.__taskModel = taskModel
        self.__frontendSettings = frontendSettings
        self._conn = QDBusConnection("Xware Desktop").sessionBus()

        self._interface = QDBusInterface(_DBUS_NOTIFY_SERVICE,
                                         _DBUS_NOTIFY_PATH,
                                         _DBUS_NOTIFY_INTERFACE, self._conn)

        self._notified = {}  # a dict of notifyId: taskDict
        self.__taskModel.taskCompleted.connect(self.notifyTaskCompleted,
                                               Qt.DirectConnection)

        self._capabilities = self._getCapabilities()
        if "actions" in self._capabilities:
            successful = self._conn.connect(_DBUS_NOTIFY_SERVICE,
                                            _DBUS_NOTIFY_PATH,
                                            _DBUS_NOTIFY_INTERFACE,
                                            "ActionInvoked",
                                            self.slotActionInvoked)
            if not successful:
                logging.error("ActionInvoked connect failed.")

        self._qSound_complete = QSound(":/sound/download-complete.wav", self)

    @property
    def isConnected(self):
        return self._conn.isConnected()

    @pyqtSlot("QObject", result="void")
    def notifyTaskCompleted(self, taskItem):
        if self.__frontendSettings.getbool("notifybysound"):
            self._qSound_complete.play()

        if not self.__frontendSettings.getbool("popnotifications"):
            return

        self._dbus_notifyCompleted(taskItem)

    def _getCapabilities(self):
        # get libnotify server caps and remember it.
        qdBusMsg = self._interface.call("GetCapabilities")
        if qdBusMsg.errorName():
            logging.error(
                "cannot get org.freedesktop.Notifications.GetCapabilities")
            return []
        else:
            return qdBusMsg.arguments()[0]

    def _dbus_notifyCompleted(self, task: "TaskItem"):
        if "actions" in self._capabilities:
            actions = QDBusArgument(["open", "打开", "viewOneFile", "在文件夹中显示"],
                                    QMetaType.QStringList)
        else:
            actions = QDBusArgument([], QMetaType.QStringList)

        qdBusMsg = self._interface.call(
            "Notify",
            QDBusArgument("Xware Desktop", QMetaType.QString),  # app_name
            QDBusArgument(0, QMetaType.UInt),  # replace_id
            QDBusArgument("xware-desktop", QMetaType.QString),  # app_icon
            QDBusArgument("下载完成", QMetaType.QString),  # summary
            QDBusArgument(task.name, QMetaType.QString),  # body
            actions,
            {
                "category": "transfer.complete",
            },  # hints
            QDBusArgument(5000, QMetaType.Int),  # timeout
        )

        if qdBusMsg.errorName():
            logging.error("DBus, notifyTask {}: {}".format(
                qdBusMsg.errorName(), qdBusMsg.errorMessage()))
        else:
            # add it to the dict
            notificationId = qdBusMsg.arguments()[0]
            self._notified[notificationId] = task.id

    @pyqtSlot(QDBusMessage)
    def slotActionInvoked(self, msg):
        notifyId, action = msg.arguments()
        taskId = self._notified.get(notifyId, None)
        if not taskId:
            # other applications' notifications
            return

        taskItem = self.__taskModel.adapterMap.get(taskId, None)
        if not taskItem:
            logging.debug("taskItem cannot be found anymore in TaskModel.")
            return

        fullpath = taskItem.fullpath  # path + name

        if action == "open":
            return systemOpen(fullpath)
        elif action == "viewOneFile":
            return viewOneFile(fullpath)
        elif action == "default":  # Unity's notify osd always have a default action.
            return
        else:
            raise Exception(
                "Unknown action from slotActionInvoked: {}.".format(action))
Esempio n. 36
0
from PyQt5.QtDBus import QDBusConnection, QDBusInterface, QDBus
from argparse import ArgumentParser
import dbus_options

parser = ArgumentParser(description="Launcher controller")
parser.add_argument("--show", dest="option", action="store_const", const=dbus_options.SHOW)
parser.add_argument("--hide", dest="option", action="store_const", const=dbus_options.HIDE)
parser.add_argument("--toggle", dest="option", action="store_const", const=dbus_options.TOGGLE)

args = parser.parse_args()

interface = QDBusInterface("org.sponja.launcher", "/", "org.sponja.launcher.server", QDBusConnection.sessionBus())


if not interface.isValid():
    print(f"DBus error: {QDBusConnection.sessionBus().lastError().message()}")
else:
    interface.call(QDBus.NoBlock, "setVisible", 2)
Esempio n. 37
0
def main():
	multiprocessing.set_start_method('spawn')

	if markups.__version_tuple__ < (2, ):
		sys.exit('Error: ReText needs PyMarkups 2.0 or newer to run.')

	# If we're running on Windows without a console, then discard stdout
	# and save stderr to a file to facilitate debugging in case of crashes.
	if sys.executable.endswith('pythonw.exe'):
		sys.stdout = open(devnull, 'w')
		sys.stderr = open('stderr.log', 'w')

	try:
		# See https://github.com/retext-project/retext/issues/399
		# and https://launchpad.net/bugs/941826
		ctypes.CDLL('libGL.so.1', ctypes.RTLD_GLOBAL)
	except OSError:
		pass

	# Needed for Qt WebEngine on Windows
	QApplication.setAttribute(Qt.ApplicationAttribute.AA_ShareOpenGLContexts)
	QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps)
	app = QApplication(sys.argv)
	app.setOrganizationName("ReText project")
	app.setApplicationName("ReText")
	app.setApplicationDisplayName("ReText")
	app.setApplicationVersion(app_version)
	app.setOrganizationDomain('mitya57.me')
	app.setDesktopFileName('me.mitya57.ReText.desktop')
	QNetworkProxyFactory.setUseSystemConfiguration(True)

	initializeDataDirs()
	RtTranslator = QTranslator()
	for path in datadirs:
		if RtTranslator.load('retext_' + globalSettings.uiLanguage,
		                     join(path, 'locale')):
			break
	QtTranslator = QTranslator()
	QtTranslator.load("qtbase_" + globalSettings.uiLanguage,
		QLibraryInfo.location(QLibraryInfo.LibraryLocation.TranslationsPath))
	app.installTranslator(RtTranslator)
	app.installTranslator(QtTranslator)

	parser = QCommandLineParser()
	parser.addHelpOption()
	parser.addVersionOption()
	previewOption = QCommandLineOption('preview',
		QApplication.translate('main', 'Open the files in preview mode'))
	newWindowOption = QCommandLineOption('new-window',
		QApplication.translate('main', 'Create a new window even if there is an existing one'))
	parser.addOption(previewOption)
	parser.addOption(newWindowOption)
	parser.addPositionalArgument('files',
		QApplication.translate('main', 'List of files to open'),
		'[files...]')

	parser.process(app)
	filesToOpen = parser.positionalArguments()

	print('Using configuration file:', settings.fileName())
	if globalSettings.appStyleSheet:
		sheetfile = QFile(globalSettings.appStyleSheet)
		sheetfile.open(QIODevice.OpenModeFlag.ReadOnly)
		app.setStyleSheet(QTextStream(sheetfile).readAll())
		sheetfile.close()
	window = ReTextWindow()

	openInExistingWindow = (globalSettings.openFilesInExistingWindow
		and not parser.isSet(newWindowOption))
	connection = QDBusConnection.sessionBus()
	if connection.isConnected() and openInExistingWindow:
		connection.registerObject('/', window, QDBusConnection.RegisterOption.ExportAllSlots)
		serviceName = 'me.mitya57.ReText'
		if not connection.registerService(serviceName) and filesToOpen:
			print('Opening the file(s) in the existing window of ReText.')
			iface = QDBusInterface(serviceName, '/', '', connection)
			for fileName in filesToOpen:
				iface.call('openFileWrapper', fileName)
			qWidgetIface = QDBusInterface(serviceName, '/', 'org.qtproject.Qt.QWidget', connection)
			qWidgetIface.call('raise')
			sys.exit(0)

	window.show()
	# ReText can change directory when loading files, so we
	# need to have a list of canonical names before loading
	fileNames = list(map(canonicalize, filesToOpen))
	readStdIn = False

	if globalSettings.openLastFilesOnStartup:
		window.restoreLastOpenedFiles()
	for fileName in fileNames:
		if QFile.exists(fileName):
			window.openFileWrapper(fileName)
			if parser.isSet(previewOption):
				window.actionPreview.setChecked(True)
				window.preview(True)
		elif fileName == '-':
			readStdIn = True

	inputData = ''
	if readStdIn and sys.stdin is not None:
		if sys.stdin.isatty():
			print('Reading stdin, press ^D to end...')
		inputData = sys.stdin.read()
	if inputData or not window.tabWidget.count():
		window.createNew(inputData)
	signal.signal(signal.SIGINT, lambda sig, frame: window.close())
	sys.exit(app.exec())
Esempio n. 38
0
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtDBus import QDBusConnection, QDBusInterface, QDBusReply

if __name__ == '__main__':
    app = QCoreApplication(sys.argv)

    if not QDBusConnection.sessionBus().isConnected():
        sys.stderr.write("Cannot connect to the D-Bus session bus.\n"
                         "To start it, run:\n"
                         "\teval `dbus-launch --auto-syntax`\n")
        sys.exit(1)

    iface = QDBusInterface('org.example.QtDBus.PingExample', '/', '',
                           QDBusConnection.sessionBus())

    if iface.isValid():
        msg = iface.call('ping', sys.argv[1] if len(sys.argv) > 1 else "")
        reply = QDBusReply(msg)

        if reply.isValid():
            sys.stdout.write("Reply was: %s\n" % reply.value())
            sys.exit()

        sys.stderr.write("Call failed: %s\n" % reply.error().message())
        sys.exit(1)

    sys.stderr.write("%s\n" %
                     QDBusConnection.sessionBus().lastError().message())
    sys.exit(1)
Esempio n. 39
0
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtDBus import QDBusConnection, QDBusInterface, QDBusReply


if __name__ == '__main__':
    app = QCoreApplication(sys.argv)

    if not QDBusConnection.sessionBus().isConnected():
        sys.stderr.write("Cannot connect to the D-Bus session bus.\n"
                "To start it, run:\n"
                "\teval `dbus-launch --auto-syntax`\n");
        sys.exit(1)

    iface = QDBusInterface('org.example.QtDBus.PingExample', '/', '',
            QDBusConnection.sessionBus())

    if iface.isValid():
        msg = iface.call('ping', sys.argv[1] if len(sys.argv) > 1 else "")
        reply = QDBusReply(msg)

        if reply.isValid():
            sys.stdout.write("Reply was: %s\n" % reply.value())
            sys.exit()

        sys.stderr.write("Call failed: %s\n" % reply.error().message())
        sys.exit(1)

    sys.stderr.write("%s\n" % QDBusConnection.sessionBus().lastError().message())
    sys.exit(1)
 def __grabSelectedScreen(self):
     """
     Private method to grab a selected screen.
     """
     snapshot = QPixmap()
     
     if Globals.isKdeDesktop():
         # Step 1: get the screen number of screen containing the cursor
         if Globals.qVersionTuple() >= (5, 10, 0):
             screen = QApplication.screenAt(QCursor.pos())
             try:
                 screenId = QApplication.screens().index(screen)
             except ValueError:
                 # default to screen 0
                 screenId = 0
         else:
             desktop = QApplication.desktop()
             screenId = desktop.screenNumber(QCursor.pos())
         
         # Step 2: grab the screen
         interface = QDBusInterface(
             "org.kde.KWin",
             "/Screenshot",
             "org.kde.kwin.Screenshot"
         )
         reply = interface.call(
             "screenshotScreen",
             screenId,
             self.__captureCursor
         )
         if self.__checkReply(reply, 1):
             filename = reply.arguments()[0]
             if filename:
                 snapshot = QPixmap(filename)
                 try:
                     os.remove(filename)
                 except OSError:
                     # just ignore it
                     pass
     elif Globals.isGnomeDesktop():
         # Step 1: grab entire desktop
         path = self.__temporaryFilename()
         interface = QDBusInterface(
             "org.gnome.Shell",
             "/org/gnome/Shell/Screenshot",
             "org.gnome.Shell.Screenshot"
         )
         reply = interface.call(
             "ScreenshotWindow",
             self.__captureDecorations,
             self.__captureCursor,
             False,
             path
         )
         if self.__checkReply(reply, 2):
             filename = reply.arguments()[1]
             if filename:
                 snapshot = QPixmap(filename)
                 try:
                     os.remove(filename)
                 except OSError:
                     # just ignore it
                     pass
                 
                 # Step 2: extract the area of the screen containing
                 #         the cursor
                 if not snapshot.isNull():
                     if Globals.qVersionTuple() >= (5, 10, 0):
                         screen = QApplication.screenAt(QCursor.pos())
                         geom = screen.geometry()
                     else:
                         desktop = QApplication.desktop()
                         screenId = desktop.screenNumber(QCursor.pos())
                         geom = desktop.screenGeometry(screenId)
                     snapshot = snapshot.copy(geom)
     
     self.grabbed.emit(snapshot)
Esempio n. 41
0
class Notifier(QObject):
    _conn = None
    _interface = None
    _notifications = None  # a dict of notifyId: taskDict
    _capabilities = None
    _completedTasksStat = None

    def __init__(self, parent):
        super().__init__(parent)
        self._conn = QDBusConnection("Xware Desktop").sessionBus()

        self._interface = QDBusInterface(_DBUS_NOTIFY_SERVICE,
                                         _DBUS_NOTIFY_PATH,
                                         _DBUS_NOTIFY_INTERFACE, self._conn)

        self._notifications = {}
        self._completedTasksStat = app.etmpy.completedTasksStat
        self._completedTasksStat.sigTaskCompleted.connect(self.notifyTask)

        self._capabilities = self._getCapabilities()
        if "actions" in self._capabilities:
            successful = self._conn.connect(_DBUS_NOTIFY_SERVICE,
                                            _DBUS_NOTIFY_PATH,
                                            _DBUS_NOTIFY_INTERFACE,
                                            "ActionInvoked",
                                            self.slotActionInvoked)
            if not successful:
                logging.error("ActionInvoked connect failed.")

        self._qSound_complete = QSound(":/sound/download-complete.wav", self)

    @property
    def isConnected(self):
        return self._conn.isConnected()

    def notifyTask(self, taskId):
        task = self._completedTasksStat.getTask(taskId)

        if task.get("state",
                    None) == 11:  # see definitions in class TaskStatistic.
            if app.settings.getbool("frontend", "notifybysound"):
                self._qSound_complete.play()
            self._dbus_notify(task)
        else:
            # TODO: Also notify if errors occur
            pass

    def _getCapabilities(self):
        # get libnotify server caps and remember it.
        qdBusMsg = self._interface.call("GetCapabilities")
        if qdBusMsg.errorName():
            logging.error(
                "cannot get org.freedesktop.Notifications.GetCapabilities")
            return []
        else:
            return qdBusMsg.arguments()[0]

    def _dbus_notify(self, task):
        if not app.settings.getbool("frontend", "popnotifications"):
            return

        if "actions" in self._capabilities:
            actions = QDBusArgument(["open", "打开", "openDir", "打开文件夹"],
                                    QMetaType.QStringList)
        else:
            actions = QDBusArgument([], QMetaType.QStringList)

        qdBusMsg = self._interface.call(
            "Notify",
            QDBusArgument("Xware Desktop", QMetaType.QString),  # app_name
            QDBusArgument(0, QMetaType.UInt),  # replace_id
            # app_icon
            QDBusArgument(os.path.join(constants.FRONTEND_DIR, "thunder.ico"),
                          QMetaType.QString),
            QDBusArgument("下载完成", QMetaType.QString),  # summary
            QDBusArgument(task["name"], QMetaType.QString),  # body
            actions,
            {
                "category": "transfer.complete",
            },  # hints
            QDBusArgument(5000, QMetaType.Int),  # timeout
        )

        if qdBusMsg.errorName():
            logging.error("DBus, notifyTask {}: {}".format(
                qdBusMsg.errorName(), qdBusMsg.errorMessage()))
        else:
            # add it to the dict
            self._notifications[qdBusMsg.arguments()[0]] = task

    @pyqtSlot(QDBusMessage)
    def slotActionInvoked(self, msg):
        notifyId, action = msg.arguments()
        task = self._notifications.get(notifyId, None)
        if not task:
            # other applications' notifications
            return
        name = task["name"]  # filename
        path = task["path"]  # location

        if action == "open":
            openPath = os.path.join(path, name)
        elif action == "openDir":
            openPath = path
        elif action == "default":  # Unity's notify osd always have a default action.
            return
        else:
            raise Exception(
                "Unknown action from slotActionInvoked: {}.".format(action))

        nativeOpenPath = app.mountsFaker.convertToNativePath(openPath)
        qUrl = QUrl.fromLocalFile(nativeOpenPath)
        QDesktopServices().openUrl(qUrl)
Esempio n. 42
0
class GetwordDaemon(QtCore.QObject):

    DBUS_NAME = "com.youdao.backend"
    DBUS_PATH = "/com/youdao/backend"
    DBUS_IFACE = "com.youdao.backend"

    hide = QtCore.pyqtSignal()
    ocrRecognized = QtCore.pyqtSignal(int, int, str, arguments=['x', 'y', 'text'])
    strokeRecognized = QtCore.pyqtSignal(int, int, str, arguments=['x', 'y', 'text'])
    ocrEnableChanged = QtCore.pyqtSignal(bool, arguments=['enabled'])
    strokeEnableChanged = QtCore.pyqtSignal(bool, arguments=['enabled'])
    cursorPositionChanged = QtCore.pyqtSignal(int, int, arguments=['x', 'y'])

    doubleCtrlReleased = QtCore.pyqtSignal()
    altPressed = QtCore.pyqtSignal()
    ctrlPressed = QtCore.pyqtSignal()
    shiftPressed = QtCore.pyqtSignal()
    altReleased = QtCore.pyqtSignal()
    ctrlReleased = QtCore.pyqtSignal()
    shiftReleased = QtCore.pyqtSignal()

    keyPressed = QtCore.pyqtSignal(str, arguments=["name"])
    keyReleased = QtCore.pyqtSignal(str, arguments=["name"])

    wheelKeyReleased = QtCore.pyqtSignal()

    def __init__(self):
        QtCore.QObject.__init__(self)
        self.session_bus = QDBusConnection.sessionBus()
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'hide', self.hideSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'ocrRecognized', self.ocrRecognizedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'strokeRecognized', self.strokeRecognizedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'ocrEnableChanged', self.ocrEnableChangedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'strokeEnableChanged', self.strokeEnableChangedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'cursorPositionChanged', self.cursorPositionChangedSlot)

        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'doubleCtrlReleased', self.doubleCtrlReleasedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'altPressed', self.altPressedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'ctrlPressed', self.ctrlPressedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'shiftPressed', self.shiftPressedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'altReleased', self.altReleasedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'ctrlReleased', self.ctrlReleasedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'shiftReleased', self.shiftReleasedSlot)

        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'keyPressed', self.keyPressedSlot)
        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'keyReleased', self.keyReleasedSlot)

        self.session_bus.connect(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE,
            'wheelKeyReleased', self.wheelKeyReleasedSlot)

        self.getword_iface = QDBusInterface(self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE, self.session_bus)

    # signal slot
    @QtCore.pyqtSlot()
    def hideSlot(self):
        self.hide.emit()

    @QtCore.pyqtSlot()
    def doubleCtrlReleasedSlot(self):
        self.doubleCtrlReleased.emit()

    @QtCore.pyqtSlot()
    def altPressedSlot(self):
        self.altPressed.emit()

    @QtCore.pyqtSlot()
    def altReleasedSlot(self):
        self.altReleased.emit()

    @QtCore.pyqtSlot()
    def ctrlPressedSlot(self):
        self.ctrlPressed.emit()

    @QtCore.pyqtSlot()
    def ctrlReleasedSlot(self):
        self.ctrlReleased.emit()

    @QtCore.pyqtSlot()
    def shiftPressedSlot(self):
        self.shiftPressed.emit()

    @QtCore.pyqtSlot()
    def shiftReleasedSlot(self):
        self.shiftReleased.emit()

    @QtCore.pyqtSlot(str)
    def keyPressedSlot(self, name):
        self.keyPressed.emit(name)

    @QtCore.pyqtSlot(str)
    def keyReleasedSlot(self, name):
        self.keyReleased.emit(name)

    @QtCore.pyqtSlot()
    def wheelKeyReleasedSlot(self):
        self.wheelKeyReleased.emit()

    @QtCore.pyqtSlot(int, int, str)
    def ocrRecognizedSlot(self, x, y, text):
        self.ocrRecognized.emit(x, y, text)

    @QtCore.pyqtSlot(int, int, str)
    def strokeRecognizedSlot(self, x, y, text):
        self.strokeRecognized.emit(x, y, text)

    @QtCore.pyqtSlot(bool)
    def ocrEnableChangedSlot(self, enable):
        self.ocrEnableChanged.emit(enable)

    @QtCore.pyqtSlot(bool)
    def strokeEnableChangedSlot(self, enable):
        self.strokeEnableChanged.emit(enable)

    @QtCore.pyqtSlot(int, int)
    def cursorPositionChangedSlot(self, x, y):
        self.cursorPositionChanged.emit(x, y)

    # method wrap
    @QtCore.pyqtSlot(str)
    def PlaySound(self, url):
        self.getword_iface.call("PlaySound", url)

    @QtCore.pyqtSlot()
    def StopSound(self):
        self.getword_iface.call("StopSound")

    @QtCore.pyqtSlot()
    def ClearStroke(self):
        self.getword_iface.call("ClearStroke")

    @QtCore.pyqtSlot(bool)
    def SetOcrEnable(self, value):
        self.getword_iface.call("SetOcrEnable", value)

    @QtCore.pyqtSlot(result=bool)
    def GetOcrEnable(self):
        return self.getword_iface.call("GetOcrEnable").arguments()[0]

    @QtCore.pyqtSlot(bool)
    def SetStrokeEnable(self, value):
        self.getword_iface.call("SetStrokeEnable", value)

    @QtCore.pyqtSlot(result=bool)
    def GetStrokeEnable(self):
        return self.getword_iface.call("GetStrokeEnable").arguments()[0]

    @QtCore.pyqtSlot(int, int)
    def EmitOcr(self, x, y):
        self.getword_iface.call("EmitOcr", x, y)

    @QtCore.pyqtSlot()
    def Quit(self):
        self.getword_iface.call("Quit")
Esempio n. 43
0
class apiBase():
	"""Call the D-Bus camera APIs, asynchronously.
		
		Methods:
			- call(function[, arg1[ ,arg2[, ...]]])
				Call the remote function.
			- get([value[, ...]])
				Get the named values from the API.
			- set({key: value[, ...]}])
				Set the named values in the API.
		
		All methods return an A* promise-like, in that you use
		`.then(cb(value))` and `.catch(cb(error))` to get the results
		of calling the function.
	"""
	def __init__(self, service, path, interface="", bus=QDBusConnection.systemBus()):
		if not QDBusConnection.systemBus().isConnected():
			log.error("Can not connect to D-Bus. Is D-Bus itself running?")
			raise Exception("D-Bus Setup Error")
		
		self.name = type(self).__name__
		self.iface = QDBusInterface(service, path, interface, bus)

		# For Asynchronous call handling.
		self.enqueuedCalls = []
		self.callInProgress = False
		self.activeCall = None
		
		log.info("Connected to D-Bus %s API at %s", self.name, self.iface.path())

		# Check for errors.
		if not self.iface.isValid():
			# Otherwise, an error occured.
			log.error("Can not connect to %s D-Bus API at %s. (%s: %s)",
				self.name, self.iface.service(),
				self.iface.lastError().name(),
				self.iface.lastError().message())
		else:
			self.iface.setTimeout(API_TIMEOUT_MS)
	
	def callSync(self, *args, warnWhenCallIsSlow=True, **kwargs):
		"""Call a camera DBus API. First arg is the function name.
			
			This is the synchronous version of the call() method. It
			is much slower to call synchronously than asynchronously!
		
			See http://doc.qt.io/qt-5/qdbusabstractinterface.html#call for details about calling.
			See https://github.com/krontech/chronos-cli/tree/master/src/api for implementation details about the API being called.
			See README.md at https://github.com/krontech/chronos-cli/tree/master/src/daemon for API documentation.
		"""
		
		#Unwrap D-Bus errors from message.
		log.debug("%s.callSync %s", self.name, tuple(args))
		
		start = perf_counter()
		msg = QDBusReply(self.iface.call(*args, **kwargs))
		end = perf_counter()
		if warnWhenCallIsSlow and (end - start > API_SLOW_WARN_MS / 1000):
			log.warn(f'slow call: {self.name}.callSync{tuple(args)} took {(end-start)*1000:.0f}ms/{API_SLOW_WARN_MS}ms.')
		
		if msg.isValid():
			return msg.value()
		else:
			if msg.error().name() == 'org.freedesktop.DBus.Error.NoReply':
				raise DBusException(f"{self.name}.callSync{tuple(args)} timed out ({API_TIMEOUT_MS}ms)")
			else:
				raise DBusException("%s: %s" % (msg.error().name(), msg.error().message()))

	def getSync(self, keyOrKeys):
		"""Call a camera API DBus get method synchronously.
		
			Convenience method for `getSync('get', [value])[0]`.
			
			Accepts key or [key, …], where keys are strings.
			
			Returns value or {key:value, …}, respectively.
			
			See control's `availableKeys` for a list of valid inputs.
		"""	
		valueList = self.callSync('get',
			[keyOrKeys] if isinstance(keyOrKeys, str) else keyOrKeys )
		return valueList[keyOrKeys] if isinstance(keyOrKeys, str) else valueList

	def setSync(self, *args):
		"""Call a camera API DBus set method synchronously.
			
			Accepts {str: value, ...} or a key and a value.
			Returns either a map of set values or the set
				value, if the second form was used.
		"""
		if len(args) == 1:
			return self.callSync('set', *args)
		elif len(args) == 2:
			return self.callSync('set', {args[0]:args[1]})[args[0]]
		else:
			raise valueError('bad args')

	def enqueueCall(self, pendingCall, coalesce: bool=True): #pendingCall is CallPromise
		"""Enqueue callback. Squash and elide calls to set for efficiency."""
		
		#Step 1: Will this call actually do anything? Elide it if not.
		anticipitoryUpdates = False #Emit update signals before sending the update to the API. Results in faster UI updates but poorer framerate.
		if coalesce and pendingCall._args[0] == 'set':
			#Elide this call if it would not change known state.
			hasNewInformation = False
			newItems = pendingCall._args[1].items()
			for key, value in newItems:
				if _camState[key] != value:
					hasNewInformation = True
					if not anticipitoryUpdates:
						break
					#Update known cam state in advance of state transition.
					log.info(f'Anticipating {key} → {value}.')
					_camState[key] = value
					for callback in apiValues._callbacks[key]:
						callback(value)
			if not hasNewInformation:
				return
		
		if coalesce and pendingCall._args[0] == 'playback':
			#Always merge playback states.
			#Take the playback state already enqueued, {}, and overlay the current playback state. (so, {a:1, b:1} + {b:2} = {a:1, b:2})
			assert type(pendingCall._args[1]) is dict, f"playback() takes a {{key:value}} dict, got {pendingCall._args[1]} of type {type(pendingCall._args[1])}."
			existingParams = [call._args[1] for call in self.enqueuedCalls if call._args[0] == 'playback']
			if not existingParams:
				self.enqueuedCalls += [pendingCall]
			else:
				#Update the parameters of the next playback call instead of enqueueing a new call.
				for k, v in pendingCall._args[1].items():
					existingParams[-1][k] = v
				
			return
		
		#Step 2: Is there already a set call pending? (Note that non-set calls act as set barriers; two sets won't get coalesced if a non-set call is between them.)
		if coalesce and [pendingCall] == self.enqueuedCalls[:1]:
			self.enqueuedCalls[-1] = pendingCall
		else:
			self.enqueuedCalls += [pendingCall]
	
	def _startNextCallback(self):
		"""Check for pending callbacks.
			
			If none are found, simply stop.
			
			Note: Needs to be manually pumped.
		"""
		if self.enqueuedCalls:
			self.callInProgress = True
			self.enqueuedCalls.pop(0)._startAsyncCall()
		else:
			self.callInProgress = False

	def call(self, *args):
		"""Call a camera DBus API. First arg is the function name. Returns a promise.
		
			See http://doc.qt.io/qt-5/qdbusabstractinterface.html#call for details about calling.
			See https://github.com/krontech/chronos-cli/tree/master/src/api for implementation details about the API being called.
			See README.md at https://github.com/krontech/chronos-cli/tree/master/src/daemon for API documentation.
		"""
		promise = CallPromise(*args, api=self)

		log.debug(f'enquing {promise}')
		self.enqueueCall(promise)
		if not self.callInProgress:
			#Don't start multiple callbacks at once, the most recent one will block.
			self._startNextCallback()
		
		return promise

	def get(self, keyOrKeys):
		"""Call a camera DBus API get method.
		
			Convenience method for `control('get', [value])[0]`.
			
			Accepts key or [key, …], where keys are strings.
			
			Returns value or {key:value, …}, respectively.
			
			See control's `availableKeys` for a list of valid inputs.
		"""
		
		return self.call(
			'get', [keyOrKeys] if isinstance(keyOrKeys, str) else keyOrKeys
		).then(lambda valueList:
			valueList[keyOrKeys] if isinstance(keyOrKeys, str) else valueList
		)

	def set(self, *args):
		"""Call a camera DBus API set method.
			
			Accepts {str: value, ...} or a key and a value.
			Returns either a map of set values or the set
				value, if the second form was used.
		"""
		
		log.debug(f'simple set call: {args}')
		if len(args) == 1:
			return self.call('set', *args)
		elif len(args) == 2:
			return self.call(
				'set', {args[0]:args[1]}
			).then(lambda valueDict: 
				valueDict[args[0]]
			)
		else:
			raise valueError('bad args')
Esempio n. 44
0
 def osdView(self, mess):
     # Send OSD
     # If DBUS daemon org.kochkin.okindd is running
     dbus_interface = QDBusInterface("org.kochkin.okindd", "/Text")
     if dbus_interface.isValid():
         dbus_interface.call('printText', 'Tvok', mess, 5000)
Esempio n. 45
0
class DBusThumbnailer(QObject):

    def __init__(self, bus, listener=None):
        super().__init__()

        self.bus = bus

        self.bus.registerObject('/', self)
        self.requests: Dict[str, Tuple[str, str, str]] = {}
        self.thumbnailer = QDBusInterface(
            'org.freedesktop.thumbnails.Thumbnailer1',
            '/org/freedesktop/thumbnails/Thumbnailer1',
            'org.freedesktop.thumbnails.Thumbnailer1',
            connection=self.bus)
        self.listener = listener

        self.bus.connect('', '', 'org.freedesktop.thumbnails.Thumbnailer1',
                         'Ready', self._receive_ready)

        self.bus.connect('', '', 'org.freedesktop.thumbnails.Thumbnailer1',
                         'Started', self._receive_started)

        self.bus.connect('', '', 'org.freedesktop.thumbnails.Thumbnailer1',
                         'Finished', self._receive_finished)

        self.bus.connect('', '', 'org.freedesktop.thumbnails.Thumbnailer1',
                         'Error', self._receive_error)

    def close(self):
        self.bus.unregisterObject('/')

    def _add_request(self, handle, data):
        self.requests[handle] = data

    def _remove_request(self, handle):
        if handle in self.requests:
            del self.requests[handle]

        if not self.requests:
            self.listener.idle()

    @pyqtSlot(QDBusMessage)
    def _receive_started(self, msg):
        handle, = msg.arguments()
        self.listener.started(handle)

    @pyqtSlot(QDBusMessage)
    def _receive_ready(self, msg):
        handle, uris = msg.arguments()
        data = self.requests[handle]
        self.listener.ready(handle, uris, data[2])

    @pyqtSlot(QDBusMessage)
    def _receive_finished(self, msg):
        handle, = msg.arguments()
        self.listener.finished(handle)
        self._remove_request(handle)

    @pyqtSlot(QDBusMessage)
    def _receive_error(self, msg):
        handle, failed_uris, error_code, message = msg.arguments()
        self.listener.error(handle, failed_uris, error_code, message)

    def _call(self, method, *args):
        msg = self.thumbnailer.call(method, *args)
        reply = QDBusReply(msg)
        if not reply.isValid():
            raise Exception("Error on method call '{}': {}: {}".format(
                method,
                reply.error().name(),
                reply.error().message()))
        else:
            return msg.arguments()

    def queue(self, files, flavor="default") -> Optional[int]:
        logger.debug("DBusThumbnailer.queue: %s  %s", files, flavor)

        if files == []:
            return None

        urls = ["file://" + urllib.parse.quote(os.path.abspath(f)) for f in files]
        mime_types = [
            mimetypes.guess_type(url)[0] or "application/octet-stream"
            for url in urls
        ]

        handle, = self._call(
            "Queue",
            dbus_as(urls),  # uris: as
            dbus_as(mime_types),  # mime_types: as
            flavor,  # flavor: s
            "foreground",  # scheduler: s
            dbus_uint(0),  # handle_to_dequeue: u
            # <arg type="u" name="handle" direction="out" />
        )

        self._add_request(handle, (urls, mime_types, flavor))
        return cast(int, handle)

    def dequeue(self, handle):
        logger.debug("DBusThumbnailer.dequeue: %s", handle)

        handle, = self._call("Dequeue", handle)
        del self.requests[handle]

    def get_supported(self):
        uri_schemes, mime_types = self._call("GetSupported")
        return (uri_schemes, mime_types)

    def get_schedulers(self):
        schedulers, = self._call("GetSchedulers")
        return schedulers

    def get_flavors(self):
        flavors, = self._call("GetFlavors")
        return flavors

    @staticmethod
    def thumbnail_from_filename(filename, flavor="normal"):
        url = "file://" + urllib.parse.quote(os.path.abspath(filename))
        digest = hashlib.md5(os.fsencode(url)).hexdigest()
        result = os.path.join(xdg.BaseDirectory.xdg_cache_home, "thumbnails", flavor, digest + ".png")
        return result

    @staticmethod
    def thumbnail_from_url(url, flavor="normal"):
        digest = hashlib.md5(os.fsencode(url)).hexdigest()
        result = os.path.join(xdg.BaseDirectory.xdg_cache_home, "thumbnails", flavor, digest + ".png")
        return result