· 6 years ago · Oct 13, 2019, 04:28 PM
1"""
2Pyperclip
3
4A cross-platform clipboard module for Python, with copy & paste functions for plain text.
5By Al Sweigart al@inventwithpython.com
6BSD License
7
8Usage:
9 import pyperclip
10 pyperclip.copy('The text to be copied to the clipboard.')
11 spam = pyperclip.paste()
12
13 if not pyperclip.is_available():
14 print("Copy functionality unavailable!")
15
16On Windows, no additional modules are needed.
17On Mac, the pyobjc module is used, falling back to the pbcopy and pbpaste cli
18 commands. (These commands should come with OS X.).
19On Linux, install xclip or xsel via package manager. For example, in Debian:
20 sudo apt-get install xclip
21 sudo apt-get install xsel
22
23Otherwise on Linux, you will need the gtk or PyQt5/PyQt4 modules installed.
24
25gtk and PyQt4 modules are not available for Python 3,
26and this module does not work with PyGObject yet.
27
28Note: There seems to be a way to get gtk on Python 3, according to:
29 https://askubuntu.com/questions/697397/python3-is-not-supporting-gtk-module
30
31Cygwin is currently not supported.
32
33Security Note: This module runs programs with these names:
34 - which
35 - where
36 - pbcopy
37 - pbpaste
38 - xclip
39 - xsel
40 - klipper
41 - qdbus
42A malicious user could rename or add programs with these names, tricking
43Pyperclip into running them with whatever permissions the Python process has.
44
45"""
46__version__ = '1.7.0'
47
48import contextlib
49import ctypes
50import os
51import platform
52import subprocess
53import sys
54import time
55import warnings
56
57from ctypes import c_size_t, sizeof, c_wchar_p, get_errno, c_wchar
58
59
60# `import PyQt4` sys.exit()s if DISPLAY is not in the environment.
61# Thus, we need to detect the presence of $DISPLAY manually
62# and not load PyQt4 if it is absent.
63HAS_DISPLAY = os.getenv("DISPLAY", False)
64
65EXCEPT_MSG = """
66 Pyperclip could not find a copy/paste mechanism for your system.
67 For more information, please visit https://pyperclip.readthedocs.io/en/latest/introduction.html#not-implemented-error """
68
69PY2 = sys.version_info[0] == 2
70
71STR_OR_UNICODE = unicode if PY2 else str # For paste(): Python 3 uses str, Python 2 uses unicode.
72
73ENCODING = 'utf-8'
74
75# The "which" unix command finds where a command is.
76if platform.system() == 'Windows':
77 WHICH_CMD = 'where'
78else:
79 WHICH_CMD = 'which'
80
81def _executable_exists(name):
82 return subprocess.call([WHICH_CMD, name],
83 stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
84
85
86
87# Exceptions
88class PyperclipException(RuntimeError):
89 pass
90
91class PyperclipWindowsException(PyperclipException):
92 def __init__(self, message):
93 message += " (%s)" % ctypes.WinError()
94 super(PyperclipWindowsException, self).__init__(message)
95
96
97def _stringifyText(text):
98 if PY2:
99 acceptedTypes = (unicode, str, int, float, bool)
100 else:
101 acceptedTypes = (str, int, float, bool)
102 if not isinstance(text, acceptedTypes):
103 raise PyperclipException('only str, int, float, and bool values can be copied to the clipboard, not %s' % (text.__class__.__name__))
104 return STR_OR_UNICODE(text)
105
106
107def init_osx_pbcopy_clipboard():
108
109 def copy_osx_pbcopy(text):
110 text = _stringifyText(text) # Converts non-str values to str.
111 p = subprocess.Popen(['pbcopy', 'w'],
112 stdin=subprocess.PIPE, close_fds=True)
113 p.communicate(input=text.encode(ENCODING))
114
115 def paste_osx_pbcopy():
116 p = subprocess.Popen(['pbpaste', 'r'],
117 stdout=subprocess.PIPE, close_fds=True)
118 stdout, stderr = p.communicate()
119 return stdout.decode(ENCODING)
120
121 return copy_osx_pbcopy, paste_osx_pbcopy
122
123
124def init_osx_pyobjc_clipboard():
125 def copy_osx_pyobjc(text):
126 '''Copy string argument to clipboard'''
127 text = _stringifyText(text) # Converts non-str values to str.
128 newStr = Foundation.NSString.stringWithString_(text).nsstring()
129 newData = newStr.dataUsingEncoding_(Foundation.NSUTF8StringEncoding)
130 board = AppKit.NSPasteboard.generalPasteboard()
131 board.declareTypes_owner_([AppKit.NSStringPboardType], None)
132 board.setData_forType_(newData, AppKit.NSStringPboardType)
133
134 def paste_osx_pyobjc():
135 "Returns contents of clipboard"
136 board = AppKit.NSPasteboard.generalPasteboard()
137 content = board.stringForType_(AppKit.NSStringPboardType)
138 return content
139
140 return copy_osx_pyobjc, paste_osx_pyobjc
141
142
143def init_gtk_clipboard():
144 global gtk
145 import gtk
146
147 def copy_gtk(text):
148 global cb
149 text = _stringifyText(text) # Converts non-str values to str.
150 cb = gtk.Clipboard()
151 cb.set_text(text)
152 cb.store()
153
154 def paste_gtk():
155 clipboardContents = gtk.Clipboard().wait_for_text()
156 # for python 2, returns None if the clipboard is blank.
157 if clipboardContents is None:
158 return ''
159 else:
160 return clipboardContents
161
162 return copy_gtk, paste_gtk
163
164
165def init_qt_clipboard():
166 global QApplication
167 # $DISPLAY should exist
168
169 # Try to import from qtpy, but if that fails try PyQt5 then PyQt4
170 try:
171 from qtpy.QtWidgets import QApplication
172 except:
173 try:
174 from PyQt5.QtWidgets import QApplication
175 except:
176 from PyQt4.QtGui import QApplication
177
178 app = QApplication.instance()
179 if app is None:
180 app = QApplication([])
181
182 def copy_qt(text):
183 text = _stringifyText(text) # Converts non-str values to str.
184 cb = app.clipboard()
185 cb.setText(text)
186
187 def paste_qt():
188 cb = app.clipboard()
189 return STR_OR_UNICODE(cb.text())
190
191 return copy_qt, paste_qt
192
193
194def init_xclip_clipboard():
195 DEFAULT_SELECTION='c'
196 PRIMARY_SELECTION='p'
197
198 def copy_xclip(text, primary=False):
199 text = _stringifyText(text) # Converts non-str values to str.
200 selection=DEFAULT_SELECTION
201 if primary:
202 selection=PRIMARY_SELECTION
203 p = subprocess.Popen(['xclip', '-selection', selection],
204 stdin=subprocess.PIPE, close_fds=True)
205 p.communicate(input=text.encode(ENCODING))
206
207 def paste_xclip(primary=False):
208 selection=DEFAULT_SELECTION
209 if primary:
210 selection=PRIMARY_SELECTION
211 p = subprocess.Popen(['xclip', '-selection', selection, '-o'],
212 stdout=subprocess.PIPE,
213 stderr=subprocess.PIPE,
214 close_fds=True)
215 stdout, stderr = p.communicate()
216 # Intentionally ignore extraneous output on stderr when clipboard is empty
217 return stdout.decode(ENCODING)
218
219 return copy_xclip, paste_xclip
220
221
222def init_xsel_clipboard():
223 DEFAULT_SELECTION='-b'
224 PRIMARY_SELECTION='-p'
225
226 def copy_xsel(text, primary=False):
227 text = _stringifyText(text) # Converts non-str values to str.
228 selection_flag = DEFAULT_SELECTION
229 if primary:
230 selection_flag = PRIMARY_SELECTION
231 p = subprocess.Popen(['xsel', selection_flag, '-i'],
232 stdin=subprocess.PIPE, close_fds=True)
233 p.communicate(input=text.encode(ENCODING))
234
235 def paste_xsel(primary=False):
236 selection_flag = DEFAULT_SELECTION
237 if primary:
238 selection_flag = PRIMARY_SELECTION
239 p = subprocess.Popen(['xsel', selection_flag, '-o'],
240 stdout=subprocess.PIPE, close_fds=True)
241 stdout, stderr = p.communicate()
242 return stdout.decode(ENCODING)
243
244 return copy_xsel, paste_xsel
245
246
247def init_klipper_clipboard():
248 def copy_klipper(text):
249 text = _stringifyText(text) # Converts non-str values to str.
250 p = subprocess.Popen(
251 ['qdbus', 'org.kde.klipper', '/klipper', 'setClipboardContents',
252 text.encode(ENCODING)],
253 stdin=subprocess.PIPE, close_fds=True)
254 p.communicate(input=None)
255
256 def paste_klipper():
257 p = subprocess.Popen(
258 ['qdbus', 'org.kde.klipper', '/klipper', 'getClipboardContents'],
259 stdout=subprocess.PIPE, close_fds=True)
260 stdout, stderr = p.communicate()
261
262 # Workaround for https://bugs.kde.org/show_bug.cgi?id=342874
263 # TODO: https://github.com/asweigart/pyperclip/issues/43
264 clipboardContents = stdout.decode(ENCODING)
265 # even if blank, Klipper will append a newline at the end
266 assert len(clipboardContents) > 0
267 # make sure that newline is there
268 assert clipboardContents.endswith('\n')
269 if clipboardContents.endswith('\n'):
270 clipboardContents = clipboardContents[:-1]
271 return clipboardContents
272
273 return copy_klipper, paste_klipper
274
275
276def init_dev_clipboard_clipboard():
277 def copy_dev_clipboard(text):
278 text = _stringifyText(text) # Converts non-str values to str.
279 if text == '':
280 warnings.warn('Pyperclip cannot copy a blank string to the clipboard on Cygwin. This is effectively a no-op.')
281 if '\r' in text:
282 warnings.warn('Pyperclip cannot handle \\r characters on Cygwin.')
283
284 fo = open('/dev/clipboard', 'wt')
285 fo.write(text)
286 fo.close()
287
288 def paste_dev_clipboard():
289 fo = open('/dev/clipboard', 'rt')
290 content = fo.read()
291 fo.close()
292 return content
293
294 return copy_dev_clipboard, paste_dev_clipboard
295
296
297def init_no_clipboard():
298 class ClipboardUnavailable(object):
299
300 def __call__(self, *args, **kwargs):
301 raise PyperclipException(EXCEPT_MSG)
302
303 if PY2:
304 def __nonzero__(self):
305 return False
306 else:
307 def __bool__(self):
308 return False
309
310 return ClipboardUnavailable(), ClipboardUnavailable()
311
312
313
314
315# Windows-related clipboard functions:
316class CheckedCall(object):
317 def __init__(self, f):
318 super(CheckedCall, self).__setattr__("f", f)
319
320 def __call__(self, *args):
321 ret = self.f(*args)
322 if not ret and get_errno():
323 raise PyperclipWindowsException("Error calling " + self.f.__name__)
324 return ret
325
326 def __setattr__(self, key, value):
327 setattr(self.f, key, value)
328
329
330def init_windows_clipboard():
331 global HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, HINSTANCE, HMENU, BOOL, UINT, HANDLE
332 from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND,
333 HINSTANCE, HMENU, BOOL, UINT, HANDLE)
334
335 windll = ctypes.windll
336 msvcrt = ctypes.CDLL('msvcrt')
337
338 safeCreateWindowExA = CheckedCall(windll.user32.CreateWindowExA)
339 safeCreateWindowExA.argtypes = [DWORD, LPCSTR, LPCSTR, DWORD, INT, INT,
340 INT, INT, HWND, HMENU, HINSTANCE, LPVOID]
341 safeCreateWindowExA.restype = HWND
342
343 safeDestroyWindow = CheckedCall(windll.user32.DestroyWindow)
344 safeDestroyWindow.argtypes = [HWND]
345 safeDestroyWindow.restype = BOOL
346
347 OpenClipboard = windll.user32.OpenClipboard
348 OpenClipboard.argtypes = [HWND]
349 OpenClipboard.restype = BOOL
350
351 safeCloseClipboard = CheckedCall(windll.user32.CloseClipboard)
352 safeCloseClipboard.argtypes = []
353 safeCloseClipboard.restype = BOOL
354
355 safeEmptyClipboard = CheckedCall(windll.user32.EmptyClipboard)
356 safeEmptyClipboard.argtypes = []
357 safeEmptyClipboard.restype = BOOL
358
359 safeGetClipboardData = CheckedCall(windll.user32.GetClipboardData)
360 safeGetClipboardData.argtypes = [UINT]
361 safeGetClipboardData.restype = HANDLE
362
363 safeSetClipboardData = CheckedCall(windll.user32.SetClipboardData)
364 safeSetClipboardData.argtypes = [UINT, HANDLE]
365 safeSetClipboardData.restype = HANDLE
366
367 safeGlobalAlloc = CheckedCall(windll.kernel32.GlobalAlloc)
368 safeGlobalAlloc.argtypes = [UINT, c_size_t]
369 safeGlobalAlloc.restype = HGLOBAL
370
371 safeGlobalLock = CheckedCall(windll.kernel32.GlobalLock)
372 safeGlobalLock.argtypes = [HGLOBAL]
373 safeGlobalLock.restype = LPVOID
374
375 safeGlobalUnlock = CheckedCall(windll.kernel32.GlobalUnlock)
376 safeGlobalUnlock.argtypes = [HGLOBAL]
377 safeGlobalUnlock.restype = BOOL
378
379 wcslen = CheckedCall(msvcrt.wcslen)
380 wcslen.argtypes = [c_wchar_p]
381 wcslen.restype = UINT
382
383 GMEM_MOVEABLE = 0x0002
384 CF_UNICODETEXT = 13
385
386 @contextlib.contextmanager
387 def window():
388 """
389 Context that provides a valid Windows hwnd.
390 """
391 # we really just need the hwnd, so setting "STATIC"
392 # as predefined lpClass is just fine.
393 hwnd = safeCreateWindowExA(0, b"STATIC", None, 0, 0, 0, 0, 0,
394 None, None, None, None)
395 try:
396 yield hwnd
397 finally:
398 safeDestroyWindow(hwnd)
399
400 @contextlib.contextmanager
401 def clipboard(hwnd):
402 """
403 Context manager that opens the clipboard and prevents
404 other applications from modifying the clipboard content.
405 """
406 # We may not get the clipboard handle immediately because
407 # some other application is accessing it (?)
408 # We try for at least 500ms to get the clipboard.
409 t = time.time() + 0.5
410 success = False
411 while time.time() < t:
412 success = OpenClipboard(hwnd)
413 if success:
414 break
415 time.sleep(0.01)
416 if not success:
417 raise PyperclipWindowsException("Error calling OpenClipboard")
418
419 try:
420 yield
421 finally:
422 safeCloseClipboard()
423
424 def copy_windows(text):
425 # This function is heavily based on
426 # http://msdn.com/ms649016#_win32_Copying_Information_to_the_Clipboard
427
428 text = _stringifyText(text) # Converts non-str values to str.
429
430 with window() as hwnd:
431 # http://msdn.com/ms649048
432 # If an application calls OpenClipboard with hwnd set to NULL,
433 # EmptyClipboard sets the clipboard owner to NULL;
434 # this causes SetClipboardData to fail.
435 # => We need a valid hwnd to copy something.
436 with clipboard(hwnd):
437 safeEmptyClipboard()
438
439 if text:
440 # http://msdn.com/ms649051
441 # If the hMem parameter identifies a memory object,
442 # the object must have been allocated using the
443 # function with the GMEM_MOVEABLE flag.
444 count = wcslen(text) + 1
445 handle = safeGlobalAlloc(GMEM_MOVEABLE,
446 count * sizeof(c_wchar))
447 locked_handle = safeGlobalLock(handle)
448
449 ctypes.memmove(c_wchar_p(locked_handle), c_wchar_p(text), count * sizeof(c_wchar))
450
451 safeGlobalUnlock(handle)
452 safeSetClipboardData(CF_UNICODETEXT, handle)
453
454 def paste_windows():
455 with clipboard(None):
456 handle = safeGetClipboardData(CF_UNICODETEXT)
457 if not handle:
458 # GetClipboardData may return NULL with errno == NO_ERROR
459 # if the clipboard is empty.
460 # (Also, it may return a handle to an empty buffer,
461 # but technically that's not empty)
462 return ""
463 return c_wchar_p(handle).value
464
465 return copy_windows, paste_windows
466
467
468def init_wsl_clipboard():
469 def copy_wsl(text):
470 text = _stringifyText(text) # Converts non-str values to str.
471 p = subprocess.Popen(['clip.exe'],
472 stdin=subprocess.PIPE, close_fds=True)
473 p.communicate(input=text.encode(ENCODING))
474
475 def paste_wsl():
476 p = subprocess.Popen(['powershell.exe', '-command', 'Get-Clipboard'],
477 stdout=subprocess.PIPE,
478 stderr=subprocess.PIPE,
479 close_fds=True)
480 stdout, stderr = p.communicate()
481 # WSL appends "\r\n" to the contents.
482 return stdout[:-2].decode(ENCODING)
483
484 return copy_wsl, paste_wsl
485
486
487# Automatic detection of clipboard mechanisms and importing is done in deteremine_clipboard():
488def determine_clipboard():
489 '''
490 Determine the OS/platform and set the copy() and paste() functions
491 accordingly.
492 '''
493
494 global Foundation, AppKit, gtk, qtpy, PyQt4, PyQt5
495
496 # Setup for the CYGWIN platform:
497 if 'cygwin' in platform.system().lower(): # Cygwin has a variety of values returned by platform.system(), such as 'CYGWIN_NT-6.1'
498 # FIXME: pyperclip currently does not support Cygwin,
499 # see https://github.com/asweigart/pyperclip/issues/55
500 if os.path.exists('/dev/clipboard'):
501 warnings.warn('Pyperclip\'s support for Cygwin is not perfect, see https://github.com/asweigart/pyperclip/issues/55')
502 return init_dev_clipboard_clipboard()
503
504 # Setup for the WINDOWS platform:
505 elif os.name == 'nt' or platform.system() == 'Windows':
506 return init_windows_clipboard()
507
508 if platform.system() == 'Linux':
509 with open('/proc/version', 'r') as f:
510 if "Microsoft" in f.read():
511 return init_wsl_clipboard()
512
513 # Setup for the MAC OS X platform:
514 if os.name == 'mac' or platform.system() == 'Darwin':
515 try:
516 import Foundation # check if pyobjc is installed
517 import AppKit
518 except ImportError:
519 return init_osx_pbcopy_clipboard()
520 else:
521 return init_osx_pyobjc_clipboard()
522
523 # Setup for the LINUX platform:
524 if HAS_DISPLAY:
525 try:
526 import gtk # check if gtk is installed
527 except ImportError:
528 pass # We want to fail fast for all non-ImportError exceptions.
529 else:
530 return init_gtk_clipboard()
531
532 if _executable_exists("xsel"):
533 return init_xsel_clipboard()
534 if _executable_exists("xclip"):
535 return init_xclip_clipboard()
536 if _executable_exists("klipper") and _executable_exists("qdbus"):
537 return init_klipper_clipboard()
538
539 try:
540 # qtpy is a small abstraction layer that lets you write applications using a single api call to either PyQt or PySide.
541 # https://pypi.python.org/pypi/QtPy
542 import qtpy # check if qtpy is installed
543 except ImportError:
544 # If qtpy isn't installed, fall back on importing PyQt4.
545 try:
546 import PyQt5 # check if PyQt5 is installed
547 except ImportError:
548 try:
549 import PyQt4 # check if PyQt4 is installed
550 except ImportError:
551 pass # We want to fail fast for all non-ImportError exceptions.
552 else:
553 return init_qt_clipboard()
554 else:
555 return init_qt_clipboard()
556 else:
557 return init_qt_clipboard()
558
559
560 return init_no_clipboard()
561
562
563def set_clipboard(clipboard):
564 '''
565 Explicitly sets the clipboard mechanism. The "clipboard mechanism" is how
566 the copy() and paste() functions interact with the operating system to
567 implement the copy/paste feature. The clipboard parameter must be one of:
568 - pbcopy
569 - pbobjc (default on Mac OS X)
570 - gtk
571 - qt
572 - xclip
573 - xsel
574 - klipper
575 - windows (default on Windows)
576 - no (this is what is set when no clipboard mechanism can be found)
577 '''
578 global copy, paste
579
580 clipboard_types = {'pbcopy': init_osx_pbcopy_clipboard,
581 'pyobjc': init_osx_pyobjc_clipboard,
582 'gtk': init_gtk_clipboard,
583 'qt': init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5'
584 'xclip': init_xclip_clipboard,
585 'xsel': init_xsel_clipboard,
586 'klipper': init_klipper_clipboard,
587 'windows': init_windows_clipboard,
588 'no': init_no_clipboard}
589
590 if clipboard not in clipboard_types:
591 raise ValueError('Argument must be one of %s' % (', '.join([repr(_) for _ in clipboard_types.keys()])))
592
593 # Sets pyperclip's copy() and paste() functions:
594 copy, paste = clipboard_types[clipboard]()
595
596
597def lazy_load_stub_copy(text):
598 '''
599 A stub function for copy(), which will load the real copy() function when
600 called so that the real copy() function is used for later calls.
601
602 This allows users to import pyperclip without having determine_clipboard()
603 automatically run, which will automatically select a clipboard mechanism.
604 This could be a problem if it selects, say, the memory-heavy PyQt4 module
605 but the user was just going to immediately call set_clipboard() to use a
606 different clipboard mechanism.
607
608 The lazy loading this stub function implements gives the user a chance to
609 call set_clipboard() to pick another clipboard mechanism. Or, if the user
610 simply calls copy() or paste() without calling set_clipboard() first,
611 will fall back on whatever clipboard mechanism that determine_clipboard()
612 automatically chooses.
613 '''
614 global copy, paste
615 copy, paste = determine_clipboard()
616 return copy(text)
617
618
619def lazy_load_stub_paste():
620 '''
621 A stub function for paste(), which will load the real paste() function when
622 called so that the real paste() function is used for later calls.
623
624 This allows users to import pyperclip without having determine_clipboard()
625 automatically run, which will automatically select a clipboard mechanism.
626 This could be a problem if it selects, say, the memory-heavy PyQt4 module
627 but the user was just going to immediately call set_clipboard() to use a
628 different clipboard mechanism.
629
630 The lazy loading this stub function implements gives the user a chance to
631 call set_clipboard() to pick another clipboard mechanism. Or, if the user
632 simply calls copy() or paste() without calling set_clipboard() first,
633 will fall back on whatever clipboard mechanism that determine_clipboard()
634 automatically chooses.
635 '''
636 global copy, paste
637 copy, paste = determine_clipboard()
638 return paste()
639
640
641def is_available():
642 return copy != lazy_load_stub_copy and paste != lazy_load_stub_paste
643
644
645# Initially, copy() and paste() are set to lazy loading wrappers which will
646# set `copy` and `paste` to real functions the first time they're used, unless
647# set_clipboard() or determine_clipboard() is called first.
648copy, paste = lazy_load_stub_copy, lazy_load_stub_paste
649
650
651__all__ = ['copy', 'paste', 'set_clipboard', 'determine_clipboard']