|
17 | 17 | On Windows, no additional modules are needed.
|
18 | 18 | On Mac, the pyobjc module is used, falling back to the pbcopy and pbpaste cli
|
19 | 19 | 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: |
21 | 23 | sudo apt-get install xclip
|
22 | 24 | sudo apt-get install xsel
|
| 25 | + sudo apt-get install wl-clipboard |
23 | 26 |
|
24 | 27 | Otherwise on Linux, you will need the PyQt5 modules installed.
|
25 | 28 |
|
|
34 | 37 | - pbpaste
|
35 | 38 | - xclip
|
36 | 39 | - xsel
|
| 40 | + - wl-copy/wl-paste |
37 | 41 | - klipper
|
38 | 42 | - qdbus
|
39 | 43 | A malicious user could rename or add programs with these names, tricking
|
40 | 44 | Pyperclip into running them with whatever permissions the Python process has.
|
41 | 45 |
|
42 | 46 | """
|
43 | 47 |
|
44 |
| -__version__ = "1.7.0" |
| 48 | +__version__ = "1.8.2" |
45 | 49 |
|
46 | 50 |
|
47 | 51 | import contextlib
|
|
74 | 78 | EXCEPT_MSG = """
|
75 | 79 | Pyperclip could not find a copy/paste mechanism for your system.
|
76 | 80 | 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 |
78 | 82 | """
|
79 | 83 |
|
80 | 84 | ENCODING = "utf-8"
|
81 | 85 |
|
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 | + ) |
87 | 102 |
|
88 | 103 |
|
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 |
96 | 106 |
|
97 | 107 |
|
98 | 108 | def _stringifyText(text) -> str:
|
@@ -229,6 +239,33 @@ def paste_xsel(primary=False):
|
229 | 239 | return copy_xsel, paste_xsel
|
230 | 240 |
|
231 | 241 |
|
| 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 | + |
232 | 269 | def init_klipper_clipboard():
|
233 | 270 | def copy_klipper(text):
|
234 | 271 | text = _stringifyText(text) # Converts non-str values to str.
|
@@ -549,6 +586,8 @@ def determine_clipboard():
|
549 | 586 |
|
550 | 587 | # Setup for the LINUX platform:
|
551 | 588 | if HAS_DISPLAY:
|
| 589 | + if os.environ.get("WAYLAND_DISPLAY") and _executable_exists("wl-copy"): |
| 590 | + return init_wl_clipboard() |
552 | 591 | if _executable_exists("xsel"):
|
553 | 592 | return init_xsel_clipboard()
|
554 | 593 | if _executable_exists("xclip"):
|
@@ -602,6 +641,7 @@ def set_clipboard(clipboard):
|
602 | 641 | "qt": init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5'
|
603 | 642 | "xclip": init_xclip_clipboard,
|
604 | 643 | "xsel": init_xsel_clipboard,
|
| 644 | + "wl-clipboard": init_wl_clipboard, |
605 | 645 | "klipper": init_klipper_clipboard,
|
606 | 646 | "windows": init_windows_clipboard,
|
607 | 647 | "no": init_no_clipboard,
|
@@ -671,7 +711,56 @@ def is_available() -> bool:
|
671 | 711 | copy, paste = lazy_load_stub_copy, lazy_load_stub_paste
|
672 | 712 |
|
673 | 713 |
|
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 | +] |
675 | 764 |
|
676 | 765 | # pandas aliases
|
677 | 766 | clipboard_get = paste
|
|
0 commit comments