From cd640e5df27c6c3c58d8cc63c16cba71c237b9a4 Mon Sep 17 00:00:00 2001 From: Nicola Guerrera Date: Mon, 22 Jan 2024 15:19:59 +0100 Subject: [PATCH 1/8] Refactor grabclipboard() for x11 and wayland MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simpified logic and made it more robust against edge cases ( see the `allowed_errors` list ). Doing error checking this way, makes the behaviour of this function for x11 and wayland platforms more silimar to darwin and windows systems. fix typo src/PIL/ImageGrab.py Co-authored-by: Ondrej Baranovič fix typo src/PIL/ImageGrab.py Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> ImageGrab: \added debian edge case to comment --- src/PIL/ImageGrab.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index a4993d3d4b8..1cb02f5f93c 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -149,18 +149,7 @@ def grabclipboard(): session_type = None if shutil.which("wl-paste") and session_type in ("wayland", None): - output = subprocess.check_output(["wl-paste", "-l"]).decode() - mimetypes = output.splitlines() - if "image/png" in mimetypes: - mimetype = "image/png" - elif mimetypes: - mimetype = mimetypes[0] - else: - mimetype = None - - args = ["wl-paste"] - if mimetype: - args.extend(["-t", mimetype]) + args = ["wl-paste", "-t", "image"] elif shutil.which("xclip") and session_type in ("x11", None): args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"] else: @@ -168,10 +157,19 @@ def grabclipboard(): raise NotImplementedError(msg) p = subprocess.run(args, capture_output=True) - err = p.stderr - if err: - msg = f"{args[0]} error: {err.strip().decode()}" + err = p.stderr.decode() + if p.returncode != 0: + allowed_errors = [ + "Nothing is copied", # wl-paste, when the clipboard is empty + "not available", # wl-paste/debian xclip, when an image isn't available + "cannot convert", # xclip, when an image isn't available + "There is no owner", # xclip, when the clipboard isn't initialized + ] + if any(e in err for e in allowed_errors): + return None + msg = f"{args[0]} error: {err.strip() if err else 'Unknown error'}" raise ChildProcessError(msg) + data = io.BytesIO(p.stdout) im = Image.open(data) im.load() From b81341ae7e62a246adabc40982d2b81ed3b7542d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Jan 2024 20:15:10 +1100 Subject: [PATCH 2/8] Only decode stderr when necessary --- src/PIL/ImageGrab.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 1cb02f5f93c..730351c0d68 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -157,17 +157,21 @@ def grabclipboard(): raise NotImplementedError(msg) p = subprocess.run(args, capture_output=True) - err = p.stderr.decode() + err = p.stderr if p.returncode != 0: allowed_errors = [ - "Nothing is copied", # wl-paste, when the clipboard is empty - "not available", # wl-paste/debian xclip, when an image isn't available - "cannot convert", # xclip, when an image isn't available - "There is no owner", # xclip, when the clipboard isn't initialized + # wl-paste, when the clipboard is empty + b"Nothing is copied", + # wl-paste/debian xclip, when an image isn't available + b"not available", + # xclip, when an image isn't available + b"cannot convert", + # xclip, when the clipboard isn't initialized + b"There is no owner", ] if any(e in err for e in allowed_errors): return None - msg = f"{args[0]} error: {err.strip() if err else 'Unknown error'}" + msg = f"{args[0]} error: {err.strip().decode() if err else 'Unknown error'}" raise ChildProcessError(msg) data = io.BytesIO(p.stdout) From d2d9240de4cafee650f11c085a7ec321240a8e3e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Jan 2024 19:26:55 +1100 Subject: [PATCH 3/8] Do not declare variable until necessary --- src/PIL/ImageGrab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 730351c0d68..ca27b520caa 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -157,7 +157,6 @@ def grabclipboard(): raise NotImplementedError(msg) p = subprocess.run(args, capture_output=True) - err = p.stderr if p.returncode != 0: allowed_errors = [ # wl-paste, when the clipboard is empty @@ -169,6 +168,7 @@ def grabclipboard(): # xclip, when the clipboard isn't initialized b"There is no owner", ] + err = p.stderr if any(e in err for e in allowed_errors): return None msg = f"{args[0]} error: {err.strip().decode() if err else 'Unknown error'}" From 6998f3476843e2f8da00eb23545aab55dc280006 Mon Sep 17 00:00:00 2001 From: Nicola Guerrera Date: Sat, 27 Jan 2024 12:08:16 +0100 Subject: [PATCH 4/8] Rearrange error handling Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/ImageGrab.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index ca27b520caa..a2c7a935103 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -171,7 +171,9 @@ def grabclipboard(): err = p.stderr if any(e in err for e in allowed_errors): return None - msg = f"{args[0]} error: {err.strip().decode() if err else 'Unknown error'}" + msg = f"{args[0]} error" + if err: + msg += f": {err.strip().decode()}" raise ChildProcessError(msg) data = io.BytesIO(p.stdout) From d3205fae192ec10497326aacb7325f5880d07b04 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Jan 2024 22:54:01 +1100 Subject: [PATCH 5/8] Simplified code --- src/PIL/ImageGrab.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index a2c7a935103..c04be521f91 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -158,7 +158,8 @@ def grabclipboard(): p = subprocess.run(args, capture_output=True) if p.returncode != 0: - allowed_errors = [ + err = p.stderr + for silent_error in [ # wl-paste, when the clipboard is empty b"Nothing is copied", # wl-paste/debian xclip, when an image isn't available @@ -167,10 +168,9 @@ def grabclipboard(): b"cannot convert", # xclip, when the clipboard isn't initialized b"There is no owner", - ] - err = p.stderr - if any(e in err for e in allowed_errors): - return None + ]: + if err in silent_error: + return None msg = f"{args[0]} error" if err: msg += f": {err.strip().decode()}" From 39cbd4f0f1bf4f40229f50aa5480b4b25eaae1a5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Jan 2024 16:31:03 +1100 Subject: [PATCH 6/8] Expanded error message strings --- src/PIL/ImageGrab.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index c04be521f91..b888e66f1c1 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -163,11 +163,11 @@ def grabclipboard(): # wl-paste, when the clipboard is empty b"Nothing is copied", # wl-paste/debian xclip, when an image isn't available - b"not available", + b" not available", # xclip, when an image isn't available - b"cannot convert", + b"cannot convert ", # xclip, when the clipboard isn't initialized - b"There is no owner", + b"xclip: Error: There is no owner for the ", ]: if err in silent_error: return None From 5efa2ade222785979c1b085be09eff5ee738c42c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Jan 2024 16:53:27 +1100 Subject: [PATCH 7/8] Added test --- Tests/test_imagegrab.py | 12 ++++++++++++ src/PIL/ImageGrab.py | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 9d3d40398f6..efef4d90807 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -119,3 +119,15 @@ def test_grabclipboard_wl_clipboard(self, ext): subprocess.call(["wl-copy"], stdin=fp) im = ImageGrab.grabclipboard() assert_image_equal_tofile(im, image_path) + + @pytest.mark.skipif( + ( + sys.platform != "linux" + or not all(shutil.which(cmd) for cmd in ("wl-paste", "wl-copy")) + ), + reason="Linux with wl-clipboard only", + ) + @pytest.mark.parametrize("arg", ("text", "--clear")) + def test_grabclipboard_wl_clipboard_errors(self, arg): + subprocess.call(["wl-copy", arg]) + assert ImageGrab.grabclipboard() is None diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index b888e66f1c1..17f5750b1f3 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -162,7 +162,11 @@ def grabclipboard(): for silent_error in [ # wl-paste, when the clipboard is empty b"Nothing is copied", - # wl-paste/debian xclip, when an image isn't available + # Ubuntu/Debian wl-paste, when the clipboard is empty + b"No selection", + # Ubuntu/Debian wl-paste, when an image isn't available + b"No suitable type of content copied", + # wl-paste or Ubuntu/Debian xclip, when an image isn't available b" not available", # xclip, when an image isn't available b"cannot convert ", From d57b5e827cfd0e9850a074a4ba27e9f5ad0c9910 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Jan 2024 16:49:44 +1100 Subject: [PATCH 8/8] Corrected check --- src/PIL/ImageGrab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 17f5750b1f3..3f3be706d96 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -173,7 +173,7 @@ def grabclipboard(): # xclip, when the clipboard isn't initialized b"xclip: Error: There is no owner for the ", ]: - if err in silent_error: + if silent_error in err: return None msg = f"{args[0]} error" if err: