python-pillow / Pillow

Python Imaging Library (Fork)
https://python-pillow.org
Other
12.15k stars 2.21k forks source link

Use xdg-desktop-portal for Image.grab() on Linux #6392

Open JakobDev opened 2 years ago

JakobDev commented 2 years ago

Making a Screenshot on Linux is little bit hacky. Especially on Wayland. @radarhere tried to solve the problem in #6312 by using gnome-screenshot, but this is not a good way. It works only on Systems with Gnome. Of course, you can also add other Screenshot tools but it is far more hacky and it will not work in Sandboxed Environments. Luckily there is a Better Way: There is a official D-Bus API for taking Screenshots. It works in every Desktop Environment in X11 and Wayland. It works also in Sandboxed and Unsandboxed Environments. Here is a Example code that uses it:

import dbus
import dbus.mainloop.glib
from dbus.mainloop.glib import DBusGMainLoop

from gi.repository import GLib

def testresp(response, results, object_path):
    print(results["uri"])

def main():
    loop = GLib.MainLoop()
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    bus = dbus.SessionBus()
    obj = bus.get_object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop")
    inter = dbus.Interface(obj, "org.freedesktop.portal.Screenshot")
    bus.add_signal_receiver(
        testresp,
        signal_name="Response",
        dbus_interface="org.freedesktop.portal.Request",
        bus_name="org.freedesktop.portal.Desktop",
        path_keyword="object_path")
    inter.Screenshot("", {})
    loop.run()

main()

It needs the dbus-python. This Lib might not be the best choice, since it might be tricky to install and the mainloop can cause problems.

You can also use this API with the Shell:

gdbus call --session --dest org.freedesktop.portal.Desktop --object-path /org/freedesktop/portal/desktop --method org.freedesktop.portal.Screenshot.Screenshot "" {}

This makes a Screenshot, but I haven't found a Way to get the response, so the Program don't know where the Screenshot has been saved.

This Issue is a note that this API exists. If someone reading this with a better knowledge of D-Bus, he or she can write a better code in a more sustainable lib and make a PR. I might also take a closer look at this when I have some free time.

radarhere commented 2 years ago

When I try running your Python code, the following dialog appears every time. That doesn't seem like the automatic behaviour that we otherwise have in ImageGrab.

Screen Shot 2022-06-25 at 10 25 22 pm

JakobDev commented 2 years ago

This Dialog should only appear when running inside a Sandbox. Not sure, if that is a KDE issue on my side. But on the other Hand, it is the only solution for making screenshots on Wayland that is not bound to a specific Desktop, so we may need to take the Pill of the Permission Screen at least on Wayland. You may open a Issue about it here.

radarhere commented 2 years ago

I don't think that opening an issue about it would lead to success.

https://github.com/flatpak/xdg-desktop-portal/issues/649#issuecomment-974830861

Bypassing the screenshot portal is unacceptable, as it defeats the point of having the portal in the first place.

If someone has a scenario where this is helpful, let us know, but I don't personally see the point of programatically taking a screenshot if I have to take a user action in the middle.

JakobDev commented 2 years ago

If someone has a scenario where this is helpful, let us know, but I don't personally see the point of programatically taking a screenshot if I have to take a user action in the middle.

The problem here is, that Wayland developers do not want apps to take Screenshots silently. The only way to do this, is using this Portal. It is already written in the post you quoted:

Applications should not be able to screenshot your desktop without your permission. Removing the backdoor should not be controversial.

Even the screenshot tool of Gnome is using it, but it is whitelisted, so it don't need to ask for permission. The problem with using this Gnome Screenshot is, that it only works on distros which are using Gnome. Another Issue is, that when the Gnome developers see that a lot of Programs using this tool to bypass the Portal, they will mostly like remove the cli argument.

I know this is a very dumb decision by the developers (which Wayland is full of), but for now we have to deal with it. Maybe Pipewire could provide a solution, but I couldn't find anything about it.

Avasam commented 2 years ago

Applications should not be able to screenshot your desktop without your permission. Removing the backdoor should not be controversial.

What about screen recording? Where I'll take multiple images per second. I agree with the idea, but this ain't it. Windows Graphics API solves it elegantly with the colored border (which the user can choose to disable). Maybe they could consider?

JakobDev commented 2 years ago

Screen recording works on Wayland with Pipewire. I have found this. But I don't know if this is really made for screenshots or how to do this in Python. Moreover it would only work on Systems which use Pipewire.

radarhere commented 1 year ago

xdg-desktop-portal plans to change this behaviour somewhat.

https://github.com/flatpak/xdg-desktop-portal/commit/c8274173f7d127f1d7e39e8b5bcaf7f0ee751f48

let applications request permissions once, and be forever able to screenshot without much hassle

That could be interesting once the next version is released.

radarhere commented 1 year ago

https://github.com/flatpak/xdg-desktop-portal/releases/tag/1.16.0 has now been released.

JakobDev commented 1 year ago

I would suggest using jeepney, because it is written in pure Python and will cause fewer problems. A example implementation can be found here.

radarhere commented 1 year ago

From the look of that example, it would seem more complicated.

Could you elaborate on what you mean by "will cause fewer problems"?

nulano commented 9 months ago

I've just tested this code with an Ubuntu 23.04 VM (which comes with xdg-desktop-portal 1.16) and it seems to work fine - I get a prompt the first time and after that it no longer asks again even after a reboot. That does seem reasonable. I do find it odd that according to https://github.com/flatpak/xdg-desktop-portal/commit/c8274173f7d127f1d7e39e8b5bcaf7f0ee751f48, this permission seems to be granted for all "unsandboxed" applications at the same time, but that is up to the xdg-desktop-portal devs to figure out.

It needs the dbus-python. This Lib might not be the best choice, since it might be tricky to install and the mainloop can cause problems.

This library was preinstalled for the system Python. However, it does seem to have some multithreading limitations (e.g. the use of a main loop), so perhaps it is not the best way to implement this. I think this might be what @JakobDev dev was referring to.

From https://pypi.org/project/dbus-python/:

dbus-python might not be the best D-Bus binding for you to use. dbus-python does not follow the principle of “In the face of ambiguity, refuse the temptation to guess”, and can’t be changed to not do so without seriously breaking compatibility.

In addition, it uses libdbus (which has known problems with multi-threaded use) and attempts to be main-loop-agnostic (which means you have to select a suitable main loop for your application).

From https://jeepney.readthedocs.io/en/latest/:

The core of Jeepney is I/O free, and the jeepney.io package contains bindings for different event loops to handle I/O. Jeepney tries to be non-magical, so you may have to write a bit more code than with other interfaces such as dbus-python or pydbus.