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
28
31
Cygwin is currently not supported.
29
32
30
33
Security Note: This module runs programs with these names:
31
- - which
32
- - where
33
34
- pbcopy
34
35
- pbpaste
35
36
- xclip
36
37
- xsel
38
+ - wl-copy/wl-paste
37
39
- klipper
38
40
- qdbus
39
41
A malicious user could rename or add programs with these names, tricking
40
42
Pyperclip into running them with whatever permissions the Python process has.
41
43
42
44
"""
43
45
44
- __version__ = "1.7.0 "
46
+ __version__ = "1.8.2 "
45
47
46
48
47
49
import contextlib
55
57
)
56
58
import os
57
59
import platform
58
- from shutil import which
60
+ from shutil import which as _executable_exists
59
61
import subprocess
60
62
import time
61
63
import warnings
74
76
EXCEPT_MSG = """
75
77
Pyperclip could not find a copy/paste mechanism for your system.
76
78
For more information, please visit
77
- https://pyperclip.readthedocs.io/en/latest/#not-implemented-error
79
+ https://pyperclip.readthedocs.io/en/latest/index.html #not-implemented-error
78
80
"""
79
81
80
82
ENCODING = "utf-8"
81
83
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"
87
84
88
-
89
- def _executable_exists (name ):
90
- return (
91
- subprocess .call (
92
- [WHICH_CMD , name ], stdout = subprocess .PIPE , stderr = subprocess .PIPE
93
- )
94
- == 0
95
- )
85
+ class PyperclipTimeoutException (PyperclipException ):
86
+ pass
96
87
97
88
98
89
def _stringifyText (text ) -> str :
@@ -229,6 +220,32 @@ def paste_xsel(primary=False):
229
220
return copy_xsel , paste_xsel
230
221
231
222
223
+ def init_wl_clipboard ():
224
+ PRIMARY_SELECTION = "-p"
225
+
226
+ def copy_wl (text , primary = False ):
227
+ text = _stringifyText (text ) # Converts non-str values to str.
228
+ args = ["wl-copy" ]
229
+ if primary :
230
+ args .append (PRIMARY_SELECTION )
231
+ if not text :
232
+ args .append ("--clear" )
233
+ subprocess .check_call (args , close_fds = True )
234
+ else :
235
+ p = subprocess .Popen (args , stdin = subprocess .PIPE , close_fds = True )
236
+ p .communicate (input = text .encode (ENCODING ))
237
+
238
+ def paste_wl (primary = False ):
239
+ args = ["wl-paste" , "-n" ]
240
+ if primary :
241
+ args .append (PRIMARY_SELECTION )
242
+ p = subprocess .Popen (args , stdout = subprocess .PIPE , close_fds = True )
243
+ stdout , _stderr = p .communicate ()
244
+ return stdout .decode (ENCODING )
245
+
246
+ return copy_wl , paste_wl
247
+
248
+
232
249
def init_klipper_clipboard ():
233
250
def copy_klipper (text ):
234
251
text = _stringifyText (text ) # Converts non-str values to str.
@@ -534,7 +551,7 @@ def determine_clipboard():
534
551
return init_windows_clipboard ()
535
552
536
553
if platform .system () == "Linux" :
537
- if which ("wslconfig.exe" ):
554
+ if _executable_exists ("wslconfig.exe" ):
538
555
return init_wsl_clipboard ()
539
556
540
557
# Setup for the macOS platform:
@@ -549,6 +566,8 @@ def determine_clipboard():
549
566
550
567
# Setup for the LINUX platform:
551
568
if HAS_DISPLAY :
569
+ if os .environ .get ("WAYLAND_DISPLAY" ) and _executable_exists ("wl-copy" ):
570
+ return init_wl_clipboard ()
552
571
if _executable_exists ("xsel" ):
553
572
return init_xsel_clipboard ()
554
573
if _executable_exists ("xclip" ):
@@ -602,6 +621,7 @@ def set_clipboard(clipboard):
602
621
"qt" : init_qt_clipboard , # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5'
603
622
"xclip" : init_xclip_clipboard ,
604
623
"xsel" : init_xsel_clipboard ,
624
+ "wl-clipboard" : init_wl_clipboard ,
605
625
"klipper" : init_klipper_clipboard ,
606
626
"windows" : init_windows_clipboard ,
607
627
"no" : init_no_clipboard ,
@@ -671,7 +691,56 @@ def is_available() -> bool:
671
691
copy , paste = lazy_load_stub_copy , lazy_load_stub_paste
672
692
673
693
674
- __all__ = ["copy" , "paste" , "set_clipboard" , "determine_clipboard" ]
694
+ def waitForPaste (timeout = None ):
695
+ """This function call blocks until a non-empty text string exists on the
696
+ clipboard. It returns this text.
697
+
698
+ This function raises PyperclipTimeoutException if timeout was set to
699
+ a number of seconds that has elapsed without non-empty text being put on
700
+ the clipboard."""
701
+ startTime = time .time ()
702
+ while True :
703
+ clipboardText = paste ()
704
+ if clipboardText != "" :
705
+ return clipboardText
706
+ time .sleep (0.01 )
707
+
708
+ if timeout is not None and time .time () > startTime + timeout :
709
+ raise PyperclipTimeoutException (
710
+ "waitForPaste() timed out after " + str (timeout ) + " seconds."
711
+ )
712
+
713
+
714
+ def waitForNewPaste (timeout = None ):
715
+ """This function call blocks until a new text string exists on the
716
+ clipboard that is different from the text that was there when the function
717
+ was first called. It returns this text.
718
+
719
+ This function raises PyperclipTimeoutException if timeout was set to
720
+ a number of seconds that has elapsed without non-empty text being put on
721
+ the clipboard."""
722
+ startTime = time .time ()
723
+ originalText = paste ()
724
+ while True :
725
+ currentText = paste ()
726
+ if currentText != originalText :
727
+ return currentText
728
+ time .sleep (0.01 )
729
+
730
+ if timeout is not None and time .time () > startTime + timeout :
731
+ raise PyperclipTimeoutException (
732
+ "waitForNewPaste() timed out after " + str (timeout ) + " seconds."
733
+ )
734
+
735
+
736
+ __all__ = [
737
+ "copy" ,
738
+ "paste" ,
739
+ "waitForPaste" ,
740
+ "waitForNewPaste" ,
741
+ "set_clipboard" ,
742
+ "determine_clipboard" ,
743
+ ]
675
744
676
745
# pandas aliases
677
746
clipboard_get = paste
0 commit comments