def _updateLayout(self): self._revision += 1 msg = QDBusMessage.createSignal("/MenuBar", "com.canonical.dbusmenu", "LayoutUpdated") msg.setArguments([ QDBusArgument(self._revision, QMetaType.UInt), QDBusArgument(DBusMenuAction.Root.value, QMetaType.Int), ]) self.__sessionService.sessionBus.send(msg)
def PropertiesChanged(self, interface, prop, values): """Sends PropertiesChanged signal through sessionBus. Args: interface: interface name prop: property name values: current property value(s) """ emptyStringListArg = QDBusArgument() emptyStringListArg.add([""], QMetaType.QStringList) self.signal.setArguments([interface, {prop: values}, emptyStringListArg]) QDBusConnection.sessionBus().send(self.signal)
def EventGroup(self, msg): events = msg.arguments() for event in events: for itemId, eventId, data, timestamp in event: self._eventHandler(itemId, eventId, data, timestamp) idErrors = QDBusArgument() idErrors.beginArray(QMetaType.Int) idErrors.endArray() reply = msg.createReply([ idErrors ]) return self.__sessionService.sessionBus.send(reply)
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
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
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)
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 GetLayout(self, msg): parentId, recursionDepth, propertyNames = msg.arguments() if parentId != 0: raise NotImplementedError( "Don't know when happens when parentId is not 0.") exitItem = newDBusMenuItem(DBusMenuAction.Exit, self._getItemProperty(DBusMenuAction.Exit), []) # toggleMainWinItem = newDBusMenuItem( # DBusMenuAction.ToggleMainWindow, # self._getItemProperty(DBusMenuAction.ToggleMainWindow), # [] # ) # settingsItem = newDBusMenuItem( # DBusMenuAction.Settings, # self._getItemProperty(DBusMenuAction.Settings), # [] # ) toggleMonitorWinItem = newDBusMenuItem( DBusMenuAction.ToggleMonitorWindow, self._getItemProperty(DBusMenuAction.ToggleMonitorWindow), []) rootItem = newDBusMenuItem( DBusMenuAction.Root, self._getItemProperty(DBusMenuAction.Root), [ toggleMonitorWinItem, exitItem, ], ) reply = msg.createReply([ QDBusArgument(self._revision, QMetaType.UInt), QDBusArgument(rootItem, QDBusArgument.Structure), ]) return self.__sessionService.sessionBus.send(reply)
def EventGroup(self, msg): events = msg.arguments() for event in events: for itemId, eventId, data, timestamp in event: self._eventHandler(itemId, eventId, data, timestamp) idErrors = QDBusArgument() idErrors.beginArray(QMetaType.Int) idErrors.endArray() reply = msg.createReply([idErrors]) return self.__sessionService.sessionBus.send(reply)
def GetGroupProperties(self, msg): itemIds, propertyNames = msg.arguments() result = QDBusArgument() result.beginArray(GroupPropertyMetaType) for itemId in itemIds: result.beginStructure() result.add(itemId, QMetaType.Int) result.add(self._getItemProperty(itemId), QMetaType.QVariantMap) result.endStructure() result.endArray() reply = msg.createReply([ result ]) return self.__sessionService.sessionBus.send(reply)
def newDBusMenuItem(action: DBusMenuAction, properties, children): item = QDBusArgument() item.beginStructure() item.add(action.value, QMetaType.Int) # id item.add(properties, QMetaType.QVariantMap) item.add(children, QMetaType.QVariantList) item.endStructure() return item
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
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 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 actions = [] if self._capabilities.actions: actions = ['default', 'Activate'] # key, name actions_arg = QDBusArgument(actions, QMetaType.QStringList) origin_url_str = qt_notification.origin().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( qt_notification.origin()) if self._capabilities.kde_origin_name and is_useful_origin: hints["x-kde-origin-name"] = origin_url_str icon = qt_notification.icon() if icon.isNull(): filename = ':/icons/qutebrowser-64x64.png' icon = QImage(filename) key = self._quirks.icon_key or "image-data" data = self._convert_image(icon) if data is not None: hints[key] = data # Titles don't support markup (except with broken servers) title = qt_notification.title() if self._quirks.escape_title: title = html.escape(title, quote=False) reply = self.interface.call( QDBus.BlockWithGui, "Notify", "qutebrowser", # application name _as_uint32(replaces_id), # replaces notification id "", # icon name/file URL, we use image-data and friends instead. title, self._format_body(qt_notification.message(), qt_notification.origin()), actions_arg, hints, -1, # timeout; -1 means 'use default' ) self._verify_message(reply, "u", QDBusMessage.ReplyMessage) notification_id = reply.arguments()[0] 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) return notification_id
def GetGroupProperties(self, msg): itemIds, propertyNames = msg.arguments() result = QDBusArgument() result.beginArray(GroupPropertyMetaType) for itemId in itemIds: result.beginStructure() result.add(itemId, QMetaType.Int) result.add(self._getItemProperty(itemId), QMetaType.QVariantMap) result.endStructure() result.endArray() reply = msg.createReply([result]) return self.__sessionService.sessionBus.send(reply)