1717On Windows, no additional modules are needed.
1818On 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
2427Otherwise on Linux, you will need the PyQt5 modules installed.
2528
2831Cygwin is currently not supported.
2932
3033Security Note: This module runs programs with these names:
31- - which
32- - where
3334 - pbcopy
3435 - pbpaste
3536 - xclip
3637 - xsel
38+ - wl-copy/wl-paste
3739 - klipper
3840 - qdbus
3941A malicious user could rename or add programs with these names, tricking
4042Pyperclip into running them with whatever permissions the Python process has.
4143
4244"""
4345
44- __version__ = "1.7.0 "
46+ __version__ = "1.8.2 "
4547
4648
4749import contextlib
5557)
5658import os
5759import platform
58- from shutil import which
60+ from shutil import which as _executable_exists
5961import subprocess
6062import time
6163import warnings
7476EXCEPT_MSG = """
7577 Pyperclip could not find a copy/paste mechanism for your system.
7678 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
7880 """
7981
8082ENCODING = "utf-8"
8183
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"
8784
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
9687
9788
9889def _stringifyText (text ) -> str :
@@ -229,6 +220,32 @@ def paste_xsel(primary=False):
229220 return copy_xsel , paste_xsel
230221
231222
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+
232249def init_klipper_clipboard ():
233250 def copy_klipper (text ):
234251 text = _stringifyText (text ) # Converts non-str values to str.
@@ -534,7 +551,7 @@ def determine_clipboard():
534551 return init_windows_clipboard ()
535552
536553 if platform .system () == "Linux" :
537- if which ("wslconfig.exe" ):
554+ if _executable_exists ("wslconfig.exe" ):
538555 return init_wsl_clipboard ()
539556
540557 # Setup for the macOS platform:
@@ -549,6 +566,8 @@ def determine_clipboard():
549566
550567 # Setup for the LINUX platform:
551568 if HAS_DISPLAY :
569+ if os .environ .get ("WAYLAND_DISPLAY" ) and _executable_exists ("wl-copy" ):
570+ return init_wl_clipboard ()
552571 if _executable_exists ("xsel" ):
553572 return init_xsel_clipboard ()
554573 if _executable_exists ("xclip" ):
@@ -602,6 +621,7 @@ def set_clipboard(clipboard):
602621 "qt" : init_qt_clipboard , # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5'
603622 "xclip" : init_xclip_clipboard ,
604623 "xsel" : init_xsel_clipboard ,
624+ "wl-clipboard" : init_wl_clipboard ,
605625 "klipper" : init_klipper_clipboard ,
606626 "windows" : init_windows_clipboard ,
607627 "no" : init_no_clipboard ,
@@ -671,7 +691,56 @@ def is_available() -> bool:
671691copy , paste = lazy_load_stub_copy , lazy_load_stub_paste
672692
673693
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+ ]
675744
676745# pandas aliases
677746clipboard_get = paste
0 commit comments