How to take screenshot via DBus org.freedesktop.portal?
-
Hi everyone,
as you might know,
QtGui.QScreen.grabWindow()
does not work under Wayland in Gnome-Shell. I used to do a simple DBus call toorg.gnome.Shell.Screenshot
, but that stopped working after Gnome 40.Since Gnome 41+ using
org.freedesktop.portal.Screenshot
seems to be the way to go. Unfortunately, due to the necessary user confirmation, it is much more complicated than a simple DBus call. As a beginner with DBus and a total newbie with QtDBus, I'm unable to get it to work.I looked at the docs and the examples, but I was unable to identify something I could transfer to my problem.
Can anyone help me or provide some information to push me in the correct direction?
PS: Here is a solution I found for
jeepney
, but I have no clue how to translate it to QtDBusfrom jeepney.bus_messages import MatchRule, message_bus from jeepney.io.blocking import Proxy, open_dbus_connection from jeepney.wrappers import MessageGenerator, new_method_call class FreedesktopPortalScreenshot(MessageGenerator): interface = "org.freedesktop.portal.Screenshot" def __init__(self): super().__init__( object_path="/org/freedesktop/portal/desktop", bus_name="org.freedesktop.portal.Desktop", ) def grab(self, parent_window, options): return new_method_call(self, "Screenshot", "sa{sv}", (parent_window, options)) if __name__ == "__main__": my_token = "my_app_1234" connection = open_dbus_connection(bus="SESSION") sender_name = connection.unique_name[1:].replace(".", "_") handle = f"/org/freedesktop/portal/desktop/request/{sender_name}/{my_token}" response_rule = MatchRule( type="signal", interface="org.freedesktop.portal.Request", path=handle ) Proxy(message_bus, connection).AddMatch(response_rule) with connection.filter(response_rule) as responses: msg = FreedesktopPortalScreenshot().grab("", {"handle_token": ("s", my_token)}) connection.send_and_get_reply(msg) response = connection.recv_until_filtered(responses, timeout=15) response_code, response_body = response.body print(f"Image URI: {response_body['uri'][1]}") ``
-
After some more tinkering, this is a first protoype with QtDBus.
I think the screenshot request is working fine, at least Gnome's screenshot dialog appears. However, I'm unable to receive the response. The
dbusScreenshotResponse
signal is never called, so I'm not able to retrieve the URI to the image file, yet.Any ideas?
import sys from PySide6 import QtDBus, QtWidgets, QtCore class Communication(QtCore.QObject): dbusScreenshotResponse = QtCore.Signal(int, object) class Screenshot(QtCore.QObject): def __init__(self): super().__init__() self.com = Communication() def grab(self): interface = QtDBus.QDBusInterface( "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Screenshot", ) reply = interface.call( "Screenshot", "", {"interactive": False, "handle_token": "myapp"} ) self.com.dbusScreenshotResponse.connect(self.responseRecevied) response_path = self.parse_response_path(reply) QtDBus.QDBusConnection.sessionBus().connect( "", response_path, "org.freedesktop.portal.Request", "Response", self.com, "dbusScreenshotResponse", ) print("sent") def parse_response_path(self, reply): """Interestingly, the response path seems to be known by the reply object, but I didn't find a way to access it, other than parsing it from the string representation...""" s = str(reply).split("[ObjectPath: ")[1] s = s.split("]")[0] return s def responseRecevied(self, *args): """This shut be triggered on screenshot confirmation, but it isn't.""" print("received") print(args) class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") grabber = Screenshot() grabber.grab() if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() app.exec()
-
Today I tried a different implementation, and I got a a bit further.
The minimal example below successfully registers as Response signal in DBUS, and it is actually called, when the screenshot dialog is confirmed. But the response argument is always empty, which is no surprise, as the signal doesn't have a type signature. But if I try to set the (correct?) signature
Response = QtCore.Signal(int, list)
, the signal is not triggered anymore.Any ideas how to go on from here?
from PySide6 import QtDBus, QtWidgets, QtCore class OrgFreedesktopPortalRequestInterface(QtDBus.QDBusAbstractInterface): Response = QtCore.Signal() def __init__(self, path, connection, parent): super().__init__( "org.freedesktop.portal.Desktop", path, "org.freedesktop.portal.Request", connection, parent, ) class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("DBUS Test") bus = QtDBus.QDBusConnection.sessionBus() base = bus.baseService()[1:].replace(".", "_") token = "myapp" object_path = f"/org/freedesktop/portal/desktop/request/{base}/{token}" request = OrgFreedesktopPortalRequestInterface(object_path, bus, self) request.Response.connect(self.gotSignal) interface = QtDBus.QDBusInterface( "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Screenshot", ) reply = interface.call( "Screenshot", "", {"interactive": False, "handle_token": token} ) print("Interface call result:", reply) def gotSignal(self, *args): print("Signal received:", args) if __name__ == "__main__": app = QtWidgets.QApplication([]) window = MainWindow() window.show() app.exec()
-
I'm looking for an answer to this dynobo, if you're still around.
For anyone else looking, read through the https://github.com/flameshot-org code.
It's not a great solution though. -
-