| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660 |
- """
- Pyperclip
- A cross-platform clipboard module for Python, with copy & paste functions for plain text.
- By Al Sweigart al@inventwithpython.com
- BSD License
- Usage:
- import pyperclip
- pyperclip.copy('The text to be copied to the clipboard.')
- spam = pyperclip.paste()
- if not pyperclip.is_available():
- print("Copy functionality unavailable!")
- On Windows, no additional modules are needed.
- On Mac, the pyobjc module is used, falling back to the pbcopy and pbpaste cli
- commands. (These commands should come with OS X.).
- On Linux, install xclip, xsel, or wl-clipboard (for "wayland" sessions) via package manager.
- For example, in Debian:
- sudo apt-get install xclip
- sudo apt-get install xsel
- sudo apt-get install wl-clipboard
- Otherwise on Linux, you will need the qtpy or PyQt5 modules installed.
- This module does not work with PyGObject yet.
- Cygwin is currently not supported.
- Security Note: This module runs programs with these names:
- - which
- - pbcopy
- - pbpaste
- - xclip
- - xsel
- - wl-copy/wl-paste
- - klipper
- - qdbus
- A malicious user could rename or add programs with these names, tricking
- Pyperclip into running them with whatever permissions the Python process has.
- """
- __version__ = '1.11.0'
- import base64
- import contextlib
- import ctypes
- import os
- import platform
- import subprocess
- import sys
- import time
- import warnings
- from ctypes import c_size_t, sizeof, c_wchar_p, get_errno, c_wchar
- from typing import Union, Optional
- _IS_RUNNING_PYTHON_2 = sys.version_info[0] == 2 # type: bool
- # For paste(): Python 3 uses str, Python 2 uses unicode.
- if _IS_RUNNING_PYTHON_2:
- # mypy complains about `unicode` for Python 2, so we ignore the type error:
- _PYTHON_STR_TYPE = unicode # type: ignore
- else:
- _PYTHON_STR_TYPE = str
- ENCODING = 'utf-8' # type: str
- try:
- # Use shutil.which() for Python 3+
- from shutil import which
- def _py3_executable_exists(name): # type: (str) -> bool
- return bool(which(name))
- _executable_exists = _py3_executable_exists
- except ImportError:
- # Use the "which" unix command for Python 2.7 and prior.
- def _py2_executable_exists(name): # type: (str) -> bool
- return subprocess.call(['which', name],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
- _executable_exists = _py2_executable_exists
- # Exceptions
- class PyperclipException(RuntimeError):
- pass
- class PyperclipWindowsException(PyperclipException):
- def __init__(self, message):
- message += " (%s)" % ctypes.WinError()
- super(PyperclipWindowsException, self).__init__(message)
- class PyperclipTimeoutException(PyperclipException):
- pass
- def init_osx_pbcopy_clipboard():
- def copy_osx_pbcopy(text):
- text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
- p = subprocess.Popen(['pbcopy', 'w'],
- stdin=subprocess.PIPE, close_fds=True)
- p.communicate(input=text.encode(ENCODING))
- def paste_osx_pbcopy():
- p = subprocess.Popen(['pbpaste', 'r'],
- stdout=subprocess.PIPE, close_fds=True)
- stdout, stderr = p.communicate()
- return stdout.decode(ENCODING)
- return copy_osx_pbcopy, paste_osx_pbcopy
- def init_osx_pyobjc_clipboard():
- def copy_osx_pyobjc(text):
- '''Copy string argument to clipboard'''
- text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
- newStr = Foundation.NSString.stringWithString_(text).nsstring()
- newData = newStr.dataUsingEncoding_(Foundation.NSUTF8StringEncoding)
- board = AppKit.NSPasteboard.generalPasteboard()
- board.declareTypes_owner_([AppKit.NSStringPboardType], None)
- board.setData_forType_(newData, AppKit.NSStringPboardType)
- def paste_osx_pyobjc():
- "Returns contents of clipboard"
- board = AppKit.NSPasteboard.generalPasteboard()
- content = board.stringForType_(AppKit.NSStringPboardType)
- return content
- return copy_osx_pyobjc, paste_osx_pyobjc
- def init_qt_clipboard():
- global QApplication
- # $DISPLAY should exist
- # Try to import from qtpy, but if that fails try PyQt5
- try:
- from qtpy.QtWidgets import QApplication
- except:
- from PyQt5.QtWidgets import QApplication
- app = QApplication.instance()
- if app is None:
- app = QApplication([])
- def copy_qt(text):
- text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
- cb = app.clipboard()
- cb.setText(text)
- def paste_qt():
- cb = app.clipboard()
- return _PYTHON_STR_TYPE(cb.text())
- return copy_qt, paste_qt
- def init_xclip_clipboard():
- DEFAULT_SELECTION='c'
- PRIMARY_SELECTION='p'
- def copy_xclip(text, primary=False):
- text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
- selection=DEFAULT_SELECTION
- if primary:
- selection=PRIMARY_SELECTION
- p = subprocess.Popen(['xclip', '-selection', selection],
- stdin=subprocess.PIPE, close_fds=True)
- p.communicate(input=text.encode(ENCODING))
- def paste_xclip(primary=False):
- selection=DEFAULT_SELECTION
- if primary:
- selection=PRIMARY_SELECTION
- p = subprocess.Popen(['xclip', '-selection', selection, '-o'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- close_fds=True)
- stdout, stderr = p.communicate()
- # Intentionally ignore extraneous output on stderr when clipboard is empty
- return stdout.decode(ENCODING)
- return copy_xclip, paste_xclip
- def init_xsel_clipboard():
- DEFAULT_SELECTION='-b'
- PRIMARY_SELECTION='-p'
- def copy_xsel(text, primary=False):
- text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
- selection_flag = DEFAULT_SELECTION
- if primary:
- selection_flag = PRIMARY_SELECTION
- p = subprocess.Popen(['xsel', selection_flag, '-i'],
- stdin=subprocess.PIPE, close_fds=True)
- p.communicate(input=text.encode(ENCODING))
- def paste_xsel(primary=False):
- selection_flag = DEFAULT_SELECTION
- if primary:
- selection_flag = PRIMARY_SELECTION
- p = subprocess.Popen(['xsel', selection_flag, '-o'],
- stdout=subprocess.PIPE, close_fds=True)
- stdout, stderr = p.communicate()
- return stdout.decode(ENCODING)
- return copy_xsel, paste_xsel
- def init_wl_clipboard():
- PRIMARY_SELECTION = "-p"
- def copy_wl(text, primary=False):
- text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
- args = ["wl-copy"]
- if primary:
- args.append(PRIMARY_SELECTION)
- if not text:
- args.append('--clear')
- subprocess.check_call(args, close_fds=True)
- else:
- pass
- p = subprocess.Popen(args, stdin=subprocess.PIPE, close_fds=True)
- p.communicate(input=text.encode(ENCODING))
- def paste_wl(primary=False):
- args = ["wl-paste", "-n", "-t", "text"]
- if primary:
- args.append(PRIMARY_SELECTION)
- p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
- stdout, _stderr = p.communicate()
- return stdout.decode(ENCODING)
- return copy_wl, paste_wl
- def init_klipper_clipboard():
- def copy_klipper(text):
- text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
- p = subprocess.Popen(
- ['qdbus', 'org.kde.klipper', '/klipper', 'setClipboardContents',
- text.encode(ENCODING)],
- stdin=subprocess.PIPE, close_fds=True)
- p.communicate(input=None)
- def paste_klipper():
- p = subprocess.Popen(
- ['qdbus', 'org.kde.klipper', '/klipper', 'getClipboardContents'],
- stdout=subprocess.PIPE, close_fds=True)
- stdout, stderr = p.communicate()
- # Workaround for https://bugs.kde.org/show_bug.cgi?id=342874
- # TODO: https://github.com/asweigart/pyperclip/issues/43
- clipboardContents = stdout.decode(ENCODING)
- # even if blank, Klipper will append a newline at the end
- assert len(clipboardContents) > 0
- # make sure that newline is there
- assert clipboardContents.endswith('\n')
- if clipboardContents.endswith('\n'):
- clipboardContents = clipboardContents[:-1]
- return clipboardContents
- return copy_klipper, paste_klipper
- def init_dev_clipboard_clipboard():
- def copy_dev_clipboard(text):
- text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
- if text == '':
- warnings.warn('Pyperclip cannot copy a blank string to the clipboard on Cygwin. This is effectively a no-op.')
- if '\r' in text:
- warnings.warn('Pyperclip cannot handle \\r characters on Cygwin.')
- fo = open('/dev/clipboard', 'wt')
- fo.write(text)
- fo.close()
- def paste_dev_clipboard():
- fo = open('/dev/clipboard', 'rt')
- content = fo.read()
- fo.close()
- return content
- return copy_dev_clipboard, paste_dev_clipboard
- def init_no_clipboard():
- class ClipboardUnavailable(object):
- def __call__(self, *args, **kwargs):
- additionalInfo = ''
- if sys.platform == 'linux':
- additionalInfo = '\nOn Linux, you can run `sudo apt-get install xclip`, `sudo apt-get install xselect` (on X11) or `sudo apt-get install wl-clipboard` (on Wayland) to install a copy/paste mechanism.'
- raise PyperclipException('Pyperclip could not find a copy/paste mechanism for your system. For more information, please visit https://pyperclip.readthedocs.io/en/latest/index.html#not-implemented-error' + additionalInfo)
- if _IS_RUNNING_PYTHON_2:
- def __nonzero__(self):
- return False
- else:
- def __bool__(self):
- return False
- return ClipboardUnavailable(), ClipboardUnavailable()
- # Windows-related clipboard functions:
- class CheckedCall(object):
- def __init__(self, f):
- super(CheckedCall, self).__setattr__("f", f)
- def __call__(self, *args):
- ret = self.f(*args)
- if not ret and get_errno():
- raise PyperclipWindowsException("Error calling " + self.f.__name__)
- return ret
- def __setattr__(self, key, value):
- setattr(self.f, key, value)
- def init_windows_clipboard():
- global HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, HINSTANCE, HMENU, BOOL, UINT, HANDLE
- from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND,
- HINSTANCE, HMENU, BOOL, UINT, HANDLE)
- windll = ctypes.windll
- msvcrt = ctypes.CDLL('msvcrt')
- safeCreateWindowExA = CheckedCall(windll.user32.CreateWindowExA)
- safeCreateWindowExA.argtypes = [DWORD, LPCSTR, LPCSTR, DWORD, INT, INT,
- INT, INT, HWND, HMENU, HINSTANCE, LPVOID]
- safeCreateWindowExA.restype = HWND
- safeDestroyWindow = CheckedCall(windll.user32.DestroyWindow)
- safeDestroyWindow.argtypes = [HWND]
- safeDestroyWindow.restype = BOOL
- OpenClipboard = windll.user32.OpenClipboard
- OpenClipboard.argtypes = [HWND]
- OpenClipboard.restype = BOOL
- safeCloseClipboard = CheckedCall(windll.user32.CloseClipboard)
- safeCloseClipboard.argtypes = []
- safeCloseClipboard.restype = BOOL
- safeEmptyClipboard = CheckedCall(windll.user32.EmptyClipboard)
- safeEmptyClipboard.argtypes = []
- safeEmptyClipboard.restype = BOOL
- safeGetClipboardData = CheckedCall(windll.user32.GetClipboardData)
- safeGetClipboardData.argtypes = [UINT]
- safeGetClipboardData.restype = HANDLE
- safeSetClipboardData = CheckedCall(windll.user32.SetClipboardData)
- safeSetClipboardData.argtypes = [UINT, HANDLE]
- safeSetClipboardData.restype = HANDLE
- safeGlobalAlloc = CheckedCall(windll.kernel32.GlobalAlloc)
- safeGlobalAlloc.argtypes = [UINT, c_size_t]
- safeGlobalAlloc.restype = HGLOBAL
- safeGlobalLock = CheckedCall(windll.kernel32.GlobalLock)
- safeGlobalLock.argtypes = [HGLOBAL]
- safeGlobalLock.restype = LPVOID
- safeGlobalUnlock = CheckedCall(windll.kernel32.GlobalUnlock)
- safeGlobalUnlock.argtypes = [HGLOBAL]
- safeGlobalUnlock.restype = BOOL
- wcslen = CheckedCall(msvcrt.wcslen)
- wcslen.argtypes = [c_wchar_p]
- wcslen.restype = UINT
- GMEM_MOVEABLE = 0x0002
- CF_UNICODETEXT = 13
- @contextlib.contextmanager
- def window():
- """
- Context that provides a valid Windows hwnd.
- """
- # we really just need the hwnd, so setting "STATIC"
- # as predefined lpClass is just fine.
- hwnd = safeCreateWindowExA(0, b"STATIC", None, 0, 0, 0, 0, 0,
- None, None, None, None)
- try:
- yield hwnd
- finally:
- safeDestroyWindow(hwnd)
- @contextlib.contextmanager
- def clipboard(hwnd):
- """
- Context manager that opens the clipboard and prevents
- other applications from modifying the clipboard content.
- """
- # We may not get the clipboard handle immediately because
- # some other application is accessing it (?)
- # We try for at least 500ms to get the clipboard.
- t = time.time() + 0.5
- success = False
- while time.time() < t:
- success = OpenClipboard(hwnd)
- if success:
- break
- time.sleep(0.01)
- if not success:
- raise PyperclipWindowsException("Error calling OpenClipboard")
- try:
- yield
- finally:
- safeCloseClipboard()
- def copy_windows(text):
- # This function is heavily based on
- # http://msdn.com/ms649016#_win32_Copying_Information_to_the_Clipboard
- text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
- with window() as hwnd:
- # http://msdn.com/ms649048
- # If an application calls OpenClipboard with hwnd set to NULL,
- # EmptyClipboard sets the clipboard owner to NULL;
- # this causes SetClipboardData to fail.
- # => We need a valid hwnd to copy something.
- with clipboard(hwnd):
- safeEmptyClipboard()
- if text:
- # http://msdn.com/ms649051
- # If the hMem parameter identifies a memory object,
- # the object must have been allocated using the
- # function with the GMEM_MOVEABLE flag.
- count = wcslen(text) + 1
- handle = safeGlobalAlloc(GMEM_MOVEABLE,
- count * sizeof(c_wchar))
- locked_handle = safeGlobalLock(handle)
- ctypes.memmove(c_wchar_p(locked_handle), c_wchar_p(text), count * sizeof(c_wchar))
- safeGlobalUnlock(handle)
- safeSetClipboardData(CF_UNICODETEXT, handle)
- def paste_windows():
- with clipboard(None):
- handle = safeGetClipboardData(CF_UNICODETEXT)
- if not handle:
- # GetClipboardData may return NULL with errno == NO_ERROR
- # if the clipboard is empty.
- # (Also, it may return a handle to an empty buffer,
- # but technically that's not empty)
- return ""
- locked_handle = safeGlobalLock(handle)
- return_value = c_wchar_p(locked_handle).value
- safeGlobalUnlock(handle)
- return return_value
- return copy_windows, paste_windows
- def init_wsl_clipboard():
- def copy_wsl(text):
- text = _PYTHON_STR_TYPE(text) # Converts non-str values to str.
- p = subprocess.Popen(['clip.exe'],
- stdin=subprocess.PIPE, close_fds=True)
- p.communicate(input=text.encode('utf-16le'))
- def paste_wsl():
- ps_script = '[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes((Get-Clipboard -Raw)))'
- # '-noprofile' speeds up load time
- p = subprocess.Popen(['powershell.exe', '-noprofile', '-command', ps_script],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- close_fds=True)
- stdout, stderr = p.communicate()
- if stderr:
- raise Exception(f"Error pasting from clipboard: {stderr}")
- try:
- base64_encoded = stdout.decode('utf-8').strip()
- decoded_bytes = base64.b64decode(base64_encoded)
- return decoded_bytes.decode('utf-8')
- except Exception as e:
- raise RuntimeError(f"Decoding error: {e}")
- return copy_wsl, paste_wsl
- # Automatic detection of clipboard mechanisms and importing is done in determine_clipboard():
- def determine_clipboard():
- '''
- Determine the OS/platform and set the copy() and paste() functions
- accordingly.
- '''
- global Foundation, AppKit, qtpy, PyQt5
- # Setup for the CYGWIN platform:
- if 'cygwin' in platform.system().lower(): # Cygwin has a variety of values returned by platform.system(), such as 'CYGWIN_NT-6.1'
- # FIXME: pyperclip currently does not support Cygwin,
- # see https://github.com/asweigart/pyperclip/issues/55
- if os.path.exists('/dev/clipboard'):
- warnings.warn('Pyperclip\'s support for Cygwin is not perfect, see https://github.com/asweigart/pyperclip/issues/55')
- return init_dev_clipboard_clipboard()
- # Setup for the WINDOWS platform:
- elif os.name == 'nt' or platform.system() == 'Windows':
- return init_windows_clipboard()
- if platform.system() == 'Linux' and os.path.isfile('/proc/version'):
- with open('/proc/version', 'r') as f:
- if "microsoft" in f.read().lower():
- return init_wsl_clipboard()
- # Setup for the MAC OS X platform:
- if os.name == 'mac' or platform.system() == 'Darwin':
- try:
- import Foundation # check if pyobjc is installed
- import AppKit
- except ImportError:
- return init_osx_pbcopy_clipboard()
- else:
- return init_osx_pyobjc_clipboard()
- # Setup for the LINUX platform:
- if os.getenv("WAYLAND_DISPLAY") and _executable_exists("wl-copy") and _executable_exists("wl-paste"):
- return init_wl_clipboard()
- # `import PyQt4` sys.exit()s if DISPLAY is not in the environment.
- # Thus, we need to detect the presence of $DISPLAY manually
- # and not load PyQt4 if it is absent.
- elif os.getenv("DISPLAY"):
- if _executable_exists("xclip"):
- # Note: 2024/06/18 Google Trends shows xclip as more popular than xsel.
- return init_xclip_clipboard()
- if _executable_exists("xsel"):
- return init_xsel_clipboard()
- if _executable_exists("klipper") and _executable_exists("qdbus"):
- return init_klipper_clipboard()
- try:
- # qtpy is a small abstraction layer that lets you write
- # applications using a single api call to either PyQt or PySide.
- # https://pypi.python.org/pypi/QtPy
- import qtpy # check if qtpy is installed
- return init_qt_clipboard()
- except ImportError:
- pass
- # If qtpy isn't installed, fall back on importing PyQt5
- try:
- import PyQt5 # check if PyQt5 is installed
- return init_qt_clipboard()
- except ImportError:
- pass
- return init_no_clipboard()
- def set_clipboard(clipboard):
- '''
- Explicitly sets the clipboard mechanism. The "clipboard mechanism" is how
- the copy() and paste() functions interact with the operating system to
- implement the copy/paste feature. The clipboard parameter must be one of:
- - pbcopy
- - pbobjc (default on Mac OS X)
- - qt
- - xclip
- - xsel
- - klipper
- - windows (default on Windows)
- - no (this is what is set when no clipboard mechanism can be found)
- '''
- global copy, paste
- clipboard_types = {
- "pbcopy": init_osx_pbcopy_clipboard,
- "pyobjc": init_osx_pyobjc_clipboard,
- "qt": init_qt_clipboard, # TODO - split this into 'qtpy' and 'pyqt5'
- "xclip": init_xclip_clipboard,
- "xsel": init_xsel_clipboard,
- "wl-clipboard": init_wl_clipboard,
- "klipper": init_klipper_clipboard,
- "windows": init_windows_clipboard,
- "no": init_no_clipboard,
- }
- if clipboard not in clipboard_types:
- raise ValueError('Argument must be one of %s' % (', '.join([repr(_) for _ in clipboard_types.keys()])))
- # Sets pyperclip's copy() and paste() functions:
- copy, paste = clipboard_types[clipboard]()
- def lazy_load_stub_copy(text):
- '''
- A stub function for copy(), which will load the real copy() function when
- called so that the real copy() function is used for later calls.
- This allows users to import pyperclip without having determine_clipboard()
- automatically run, which will automatically select a clipboard mechanism.
- This could be a problem if it selects, say, the memory-heavy PyQt5 module
- but the user was just going to immediately call set_clipboard() to use a
- different clipboard mechanism.
- The lazy loading this stub function implements gives the user a chance to
- call set_clipboard() to pick another clipboard mechanism. Or, if the user
- simply calls copy() or paste() without calling set_clipboard() first,
- will fall back on whatever clipboard mechanism that determine_clipboard()
- automatically chooses.
- '''
- global copy, paste
- copy, paste = determine_clipboard()
- return copy(text)
- def lazy_load_stub_paste():
- '''
- A stub function for paste(), which will load the real paste() function when
- called so that the real paste() function is used for later calls.
- This allows users to import pyperclip without having determine_clipboard()
- automatically run, which will automatically select a clipboard mechanism.
- This could be a problem if it selects, say, the memory-heavy PyQt5 module
- but the user was just going to immediately call set_clipboard() to use a
- different clipboard mechanism.
- The lazy loading this stub function implements gives the user a chance to
- call set_clipboard() to pick another clipboard mechanism. Or, if the user
- simply calls copy() or paste() without calling set_clipboard() first,
- will fall back on whatever clipboard mechanism that determine_clipboard()
- automatically chooses.
- '''
- global copy, paste
- copy, paste = determine_clipboard()
- return paste()
- def is_available():
- return copy != lazy_load_stub_copy and paste != lazy_load_stub_paste
- # Initially, copy() and paste() are set to lazy loading wrappers which will
- # set `copy` and `paste` to real functions the first time they're used, unless
- # set_clipboard() or determine_clipboard() is called first.
- copy, paste = lazy_load_stub_copy, lazy_load_stub_paste
- __all__ = ['copy', 'paste', 'set_clipboard', 'determine_clipboard']
|