Skip to content

Commit 59e26b7

Browse files
committed
ENH: update bundled pyperclip with changes from 1.8.2 release
Copy the changes from upstream 1.8.2 to the bundled copy of pyperclip. The code was reformatted using black and verified using ruff. The existing modifications from pandas were preserved.
1 parent d04747c commit 59e26b7

File tree

1 file changed

+105
-16
lines changed

1 file changed

+105
-16
lines changed

pandas/io/clipboard/__init__.py

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
On Windows, no additional modules are needed.
1818
On Mac, the pyobjc module is used, falling back to the pbcopy and pbpaste cli
1919
commands. (These commands should come with OS X.).
20-
On Linux, install xclip or xsel via package manager. For example, in Debian:
20+
On Linux, install xclip, xsel, or wl-clipboard (for "wayland" sessions) via
21+
package manager.
22+
For example, in Debian:
2123
sudo apt-get install xclip
2224
sudo apt-get install xsel
25+
sudo apt-get install wl-clipboard
2326
2427
Otherwise on Linux, you will need the PyQt5 modules installed.
2528
@@ -34,14 +37,15 @@
3437
- pbpaste
3538
- xclip
3639
- xsel
40+
- wl-copy/wl-paste
3741
- klipper
3842
- qdbus
3943
A malicious user could rename or add programs with these names, tricking
4044
Pyperclip into running them with whatever permissions the Python process has.
4145
4246
"""
4347

44-
__version__ = "1.7.0"
48+
__version__ = "1.8.2"
4549

4650

4751
import contextlib
@@ -74,25 +78,31 @@
7478
EXCEPT_MSG = """
7579
Pyperclip could not find a copy/paste mechanism for your system.
7680
For more information, please visit
77-
https://pyperclip.readthedocs.io/en/latest/#not-implemented-error
81+
https://pyperclip.readthedocs.io/en/latest/index.html#not-implemented-error
7882
"""
7983

8084
ENCODING = "utf-8"
8185

82-
# The "which" unix command finds where a command is.
83-
if platform.system() == "Windows":
84-
WHICH_CMD = "where"
85-
else:
86-
WHICH_CMD = "which"
86+
try:
87+
from shutil import which as _executable_exists
88+
except ImportError:
89+
# The "which" unix command finds where a command is.
90+
if platform.system() == "Windows":
91+
WHICH_CMD = "where"
92+
else:
93+
WHICH_CMD = "which"
94+
95+
def _executable_exists(name):
96+
return (
97+
subprocess.call(
98+
[WHICH_CMD, name], stdout=subprocess.PIPE, stderr=subprocess.PIPE
99+
)
100+
== 0
101+
)
87102

88103

89-
def _executable_exists(name):
90-
return (
91-
subprocess.call(
92-
[WHICH_CMD, name], stdout=subprocess.PIPE, stderr=subprocess.PIPE
93-
)
94-
== 0
95-
)
104+
class PyperclipTimeoutException(PyperclipException):
105+
pass
96106

97107

98108
def _stringifyText(text) -> str:
@@ -229,6 +239,33 @@ def paste_xsel(primary=False):
229239
return copy_xsel, paste_xsel
230240

231241

242+
def init_wl_clipboard():
243+
PRIMARY_SELECTION = "-p"
244+
245+
def copy_wl(text, primary=False):
246+
text = _stringifyText(text) # Converts non-str values to str.
247+
args = ["wl-copy"]
248+
if primary:
249+
args.append(PRIMARY_SELECTION)
250+
if not text:
251+
args.append("--clear")
252+
subprocess.check_call(args, close_fds=True)
253+
else:
254+
pass
255+
p = subprocess.Popen(args, stdin=subprocess.PIPE, close_fds=True)
256+
p.communicate(input=text.encode(ENCODING))
257+
258+
def paste_wl(primary=False):
259+
args = ["wl-paste", "-n"]
260+
if primary:
261+
args.append(PRIMARY_SELECTION)
262+
p = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True)
263+
stdout, _stderr = p.communicate()
264+
return stdout.decode(ENCODING)
265+
266+
return copy_wl, paste_wl
267+
268+
232269
def init_klipper_clipboard():
233270
def copy_klipper(text):
234271
text = _stringifyText(text) # Converts non-str values to str.
@@ -549,6 +586,8 @@ def determine_clipboard():
549586

550587
# Setup for the LINUX platform:
551588
if HAS_DISPLAY:
589+
if os.environ.get("WAYLAND_DISPLAY") and _executable_exists("wl-copy"):
590+
return init_wl_clipboard()
552591
if _executable_exists("xsel"):
553592
return init_xsel_clipboard()
554593
if _executable_exists("xclip"):
@@ -602,6 +641,7 @@ def set_clipboard(clipboard):
602641
"qt": init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5'
603642
"xclip": init_xclip_clipboard,
604643
"xsel": init_xsel_clipboard,
644+
"wl-clipboard": init_wl_clipboard,
605645
"klipper": init_klipper_clipboard,
606646
"windows": init_windows_clipboard,
607647
"no": init_no_clipboard,
@@ -671,7 +711,56 @@ def is_available() -> bool:
671711
copy, paste = lazy_load_stub_copy, lazy_load_stub_paste
672712

673713

674-
__all__ = ["copy", "paste", "set_clipboard", "determine_clipboard"]
714+
def waitForPaste(timeout=None):
715+
"""This function call blocks until a non-empty text string exists on the
716+
clipboard. It returns this text.
717+
718+
This function raises PyperclipTimeoutException if timeout was set to
719+
a number of seconds that has elapsed without non-empty text being put on
720+
the clipboard."""
721+
startTime = time.time()
722+
while True:
723+
clipboardText = paste()
724+
if clipboardText != "":
725+
return clipboardText
726+
time.sleep(0.01)
727+
728+
if timeout is not None and time.time() > startTime + timeout:
729+
raise PyperclipTimeoutException(
730+
"waitForPaste() timed out after " + str(timeout) + " seconds."
731+
)
732+
733+
734+
def waitForNewPaste(timeout=None):
735+
"""This function call blocks until a new text string exists on the
736+
clipboard that is different from the text that was there when the function
737+
was first called. It returns this text.
738+
739+
This function raises PyperclipTimeoutException if timeout was set to
740+
a number of seconds that has elapsed without non-empty text being put on
741+
the clipboard."""
742+
startTime = time.time()
743+
originalText = paste()
744+
while True:
745+
currentText = paste()
746+
if currentText != originalText:
747+
return currentText
748+
time.sleep(0.01)
749+
750+
if timeout is not None and time.time() > startTime + timeout:
751+
raise PyperclipTimeoutException(
752+
"waitForNewPaste() timed out after " + str(timeout) + " seconds."
753+
)
754+
755+
756+
__all__ = [
757+
"copy",
758+
"paste",
759+
"waitForPaste",
760+
"waitForNewPaste",
761+
"set_clipboard",
762+
"determine_clipboard",
763+
]
675764

676765
# pandas aliases
677766
clipboard_get = paste

0 commit comments

Comments
 (0)