Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. How to take screenshot via DBus org.freedesktop.portal?
QtWS25 Last Chance

How to take screenshot via DBus org.freedesktop.portal?

Scheduled Pinned Locked Moved Unsolved Qt for Python
qt for pythonpyside
6 Posts 3 Posters 2.0k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    dynobo
    wrote on 28 May 2022, 19:17 last edited by
    #1

    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 to org.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 QtDBus

    from 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]}")
    ``
    1 Reply Last reply
    0
    • D Offline
      D Offline
      dynobo
      wrote on 28 May 2022, 23:02 last edited by dynobo
      #2

      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()
      
      1 Reply Last reply
      0
      • D Offline
        D Offline
        dynobo
        wrote on 30 Nov 2022, 19:42 last edited by
        #3

        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()
        
        1 Reply Last reply
        0
        • G Offline
          G Offline
          Grant Carthew
          wrote on 30 May 2024, 10:49 last edited by
          #4

          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.

          1 Reply Last reply
          0
          • F Offline
            F Offline
            friedemannkleint
            wrote on 30 May 2024, 18:46 last edited by
            #5

            Have you checked https://doc.qt.io/qtforpython-6/PySide6/QtMultimedia/QScreenCapture.html#PySide6.QtMultimedia.QScreenCapture and https://doc.qt.io/qtforpython-6/PySide6/QtMultimedia/QWindowCapture.html#PySide6.QtMultimedia.QWindowCapture - do they work for Wayland?

            1 Reply Last reply
            0
            • F Offline
              F Offline
              friedemannkleint
              wrote on 3 Jun 2024, 06:21 last edited by
              #6

              See also https://bugreports.qt.io/browse/QTBUG-121452

              1 Reply Last reply
              1

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved