How can I get a screenshot on Wayland with pure Python?

Viewed 447

As of the latest Pillow version (9.1.0), the grab() function to get a screenshot only works on X11 and not Wayland. When I disable Wayland, it works fine.

How can I get screenshot on Wayland with pure Python? Ideally the solution doesn't involve relying on other libraries.

Is this even possible for an app to get a full screenshot (including the windows of other apps) given Wayland's security model? Using scrot to get a screenshot results in a completely black image (similar to the issue here). However, gnome-screenshot works just fine so I know this is possible.

2 Answers

Although there is a Wayland protocol extension wlr-screencopy-unstable-v1 to take screenshots (by interacting directly with the Wayland compositor), its not a practical solution because many Compositors (for example GNOME/Mutter) don't implement this extension.

A better solution is to access the Screenshot functionality via dbus using the XDG portal org.freedesktop.portal.Desktop service.

This also ensures that the application runs in flatpak sandboxes.

Minimal example:

import dbus
from gi.repository import GLib
import dbus.mainloop.glib


def response_handler(response, result):
    if response == 0:
        print(f'Screenshot file: {result.get("uri")}')
    else:
        print("Failed to get screenshot")


def main():
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    bus = dbus.SessionBus()
    my_name = bus.get_connection().get_unique_name()[1:].replace(".", "_")
    response_path = f"/org/freedesktop/portal/desktop/request/{my_name}/my_token"
    bus.add_signal_receiver(
        response_handler,
        dbus_interface="org.freedesktop.portal.Request",
        path=response_path,
    )

    desktop = bus.get_object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop")
    desktop.Screenshot("Screenshot", {"handle_token": "my_token"}, dbus_interface="org.freedesktop.portal.Screenshot")
    loop = GLib.MainLoop()
    loop.run()


if __name__ == "__main__":
    main()

From what it looks like, there's no clear way of achieving this, so you might have to try a few possible avenues. This is basically a summary of all information I've gathered on this task:

  1. Try using pyscreenshot, since they claim Wayland support. t
  2. If you look at this thread, especially @surajRathi's comment, you'll see he has implemented a solution similar to the SO answer you have linked in the question, but in Python. This seems to be the closest solution to the problem statement.
  3. This comment on a Pillow Issue on GitHub also outlines the fact that there's no standard approach, so you'll have to try whatever works.
Related