From 28f78bbde853e122c58b4ece4c4794a85a2b76f0 Mon Sep 17 00:00:00 2001 From: Nuno Brum Date: Thu, 2 Jan 2020 02:24:40 +0100 Subject: [PATCH 1/5] Create pythonpublish.yml --- .github/workflows/pythonpublish.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/pythonpublish.yml diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml new file mode 100644 index 0000000..21f2f01 --- /dev/null +++ b/.github/workflows/pythonpublish.yml @@ -0,0 +1,26 @@ +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* From cae08ed4ba75e30cbed94adf13a0706ad68325ae Mon Sep 17 00:00:00 2001 From: Nuno Brum Date: Thu, 2 Jan 2020 02:44:47 +0100 Subject: [PATCH 2/5] Added support for multiple Clipboard formats. Now copy and paste have an optional parameter on the windows version that specifies which clipboard format is to be used. It does so while maintaining compatibility with the usual usage: copy(text) text=paste() The usage with formats is the following copy(text, clipboard_format) paste(clipboard_format) Also, multiple formats can be copied if using copy({CF_UNICODETEXT: "this is the unicode text", CF_TEXT: b"This is the ascii text", CF_BITMAP: bitmap_image}) paste_dict = paste([CF_TEXT, CF_UNICODETEXT]) # Pastes only these two paste_all = paste([]) # This pastes all available formats into a dictionary --- README.md | 16 ++- src/pyperclip/__init__.py | 233 +++++++++++++++++++++++++++++++++----- 2 files changed, 218 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 4016811..8776abe 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,22 @@ Example Usage >>> pyperclip.paste() 'The text to be copied to the clipboard.' - Currently only handles plaintext. -On Windows, no additional modules are needed. +On Windows, no additional modules are needed. It also allows to access other clipboard formats other than the CF_UNICODETEXT. +It does so while maintaining compatibility with the usage described above. + + >>> import pyperclip + >>> pyperclip.copy(html_text, 49460) # The HTML Format + >>> html_in_clipboard = pyperclip.paste(49460) + +Also, multiple formats can be copied if using + + >>> pyperclip.copy({CF_UNICODETEXT: "this is the unicode text", + CF_TEXT: b"This is the ascii text", + CF_BITMAP: bitmap_image}) + >>> paste_dict = pyperclip.paste([CF_TEXT, CF_UNICODETEXT]) # Pastes only these two + >>> paste_all = pyperclip.paste([]) # This pastes all available formats into a dictionary On Mac, this module makes use of the pbcopy and pbpaste commands, which should come with the os. diff --git a/src/pyperclip/__init__.py b/src/pyperclip/__init__.py index 3c2ea6c..e527623 100644 --- a/src/pyperclip/__init__.py +++ b/src/pyperclip/__init__.py @@ -329,8 +329,14 @@ def __setattr__(self, key, value): def init_windows_clipboard(): global HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, HINSTANCE, HMENU, BOOL, UINT, HANDLE + global CF_BITMAP, CF_DIB, CF_DIBV5, CF_DIF, CF_DSPBITMAP, CF_DSPENHMETAFILE, CF_DSPMETAFILEPICT, CF_DSPTEXT + global CF_ENHMETAFILE, CF_GDIOBJFIRST, CF_GDIOBJLAST, CF_HDROP, CF_LOCALE, CF_METAFILEPICT, CF_OEMTEXT + global CF_OWNERDISPLAY, CF_PALETTE, CF_PENDATA, CF_PRIVATEFIRST, CF_PRIVATELAST, CF_RIFF, CF_SYLK, CF_TEXT + global CF_TIFF, CF_UNICODETEXT, CF_WAVE, CF_ALL + from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, - HINSTANCE, HMENU, BOOL, UINT, HANDLE) + HINSTANCE, HMENU, BOOL, UINT, HANDLE, CHAR) + from ctypes import string_at windll = ctypes.windll msvcrt = ctypes.CDLL('msvcrt') @@ -352,6 +358,14 @@ def init_windows_clipboard(): safeCloseClipboard.argtypes = [] safeCloseClipboard.restype = BOOL + safeEnumClipboardFormats = CheckedCall(windll.user32.EnumClipboardFormats) + safeEnumClipboardFormats.argtypes = [INT] + safeEnumClipboardFormats.restype = UINT + + safeGetClipboardFormatName = CheckedCall(windll.user32.GetClipboardFormatNameW) + safeGetClipboardFormatName.argtypes = [INT, LPCSTR] + safeGetClipboardFormatName.restype = UINT + safeEmptyClipboard = CheckedCall(windll.user32.EmptyClipboard) safeEmptyClipboard.argtypes = [] safeEmptyClipboard.restype = BOOL @@ -376,13 +390,120 @@ def init_windows_clipboard(): safeGlobalUnlock.argtypes = [HGLOBAL] safeGlobalUnlock.restype = BOOL + safeGlobalSize = CheckedCall(windll.kernel32.GlobalSize) + safeGlobalSize.argtypes = [HGLOBAL] + safeGlobalSize.restyoe = UINT + wcslen = CheckedCall(msvcrt.wcslen) wcslen.argtypes = [c_wchar_p] wcslen.restype = UINT GMEM_MOVEABLE = 0x0002 - CF_UNICODETEXT = 13 + # Constant = value # Description + CF_BITMAP = 2 # A handle to a bitmap (HBITMAP). + CF_DIB = 8 # A memory object containing a BITMAPINFO structure followed by the bitmap bits. + CF_DIBV5 = 17 # A memory object containing a BITMAPV5HEADER structure followed by the bitmap color + # space information and the bitmap bits. + CF_DIF = 5 # Software Arts' Data Interchange Format. + CF_DSPBITMAP = 0x0082 # Bitmap display format associated with a private format. The hMem parameter must be a + # handle to data that can be displayed in bitmap format in lieu of the privately + # formatted data. + CF_DSPENHMETAFILE = 0x008E # Enhanced metafile display format associated with a private format. + # The hMem parameter must be a handle to data that can be displayed in enhanced metafile + # format in lieu of the privately formatted data. + CF_DSPMETAFILEPICT = 0x0083 # Metafile-picture display format associated with a private format. The hMem parameter + # must be a handle to data that can be displayed in metafile-picture format in lieu of + # the privately formatted data. + CF_DSPTEXT = 0x0081 # Text display format associated with a private format. The hMem parameter must be a + # handle to data that can be displayed in text format in lieu of the privately formatted + # data. + CF_ENHMETAFILE = 14 # A handle to an enhanced metafile (HENHMETAFILE). + CF_GDIOBJFIRST = 0x0300 # Start of a range of integer values for application-defined GDI object clipboard + # formats. The end of the range is CF_GDIOBJLAST. + # Handles associated with clipboard formats in this range are not automatically deleted + # using the GlobalFree function when the clipboard is emptied. Also, when using values + # in this range, the hMem parameter is not a handle to a GDI object, but is a handle + # allocated by the GlobalAlloc function with the GMEM_MOVEABLE flag. + CF_GDIOBJLAST = 0x03FF # See CF_GDIOBJFIRST. + CF_HDROP = 15 # A handle to type HDROP that identifies a list of files. An application can retrieve + # information about the files by passing the handle to the DragQueryFile function. + CF_LOCALE = 16 # The data is a handle to the locale identifier associated with text in the clipboard. + # When you close the clipboard, if it contains CF_TEXT data but no CF_LOCALE data, the + # system automatically sets the CF_LOCALE format to the current input language. You can + # use the CF_LOCALE format to associate a different locale with the clipboard text. + # An application that pastes text from the clipboard can retrieve this format to + # determine which character set was used to generate the text. + # Note that the clipboard does not support plain text in multiple character sets. + # To achieve this, use a formatted text data type such as RTF instead. + # The system uses the code page associated with CF_LOCALE to implicitly convert from + # CF_TEXT to CF_UNICODETEXT. Therefore, the correct code page table is used for the + # conversion. + CF_METAFILEPICT = 3 # Handle to a metafile picture format as defined by the METAFILEPICT structure. When + # passing a CF_METAFILEPICT handle by means of DDE, the application responsible for + # deleting hMem should also free the metafile referred to by the CF_METAFILEPICT handle. + CF_OEMTEXT = 7 # Text format containing characters in the OEM character set. Each line ends with a + # carriage return/linefeed (CR-LF) combination. A null character signals the end of the + # data. + CF_OWNERDISPLAY = 0x0080 # Owner-display format. The clipboard owner must display and update the clipboard viewer + # window, and receive the WM_ASKCBFORMATNAME, WM_HSCROLLCLIPBOARD, WM_PAINTCLIPBOARD, + # WM_SIZECLIPBOARD, and WM_VSCROLLCLIPBOARD messages. The hMem parameter must be NULL. + CF_PALETTE = 9 # Handle to a color palette. Whenever an application places data in the clipboard that + # depends on or assumes a color palette, it should place the palette on the clipboard as + # well. + # If the clipboard contains data in the CF_PALETTE (logical color palette) format, the + # application should use the SelectPalette and RealizePalette functions to realize + # (compare) any other data in the clipboard against that logical palette. + # When displaying clipboard data, the clipboard always uses as its current palette any + # object on the clipboard that is in the CF_PALETTE format. + CF_PENDATA = 10 # Data for the pen extensions to the Microsoft Windows for Pen Computing. + CF_PRIVATEFIRST = 0x0200 # Start of a range of integer values for private clipboard formats. The range ends with + # CF_PRIVATELAST. Handles associated with private clipboard formats are not freed + # automatically; the clipboard owner must free such handles, typically in response to + # the WM_DESTROYCLIPBOARD message. + CF_PRIVATELAST = 0x02FF # See CF_PRIVATEFIRST. + CF_RIFF = 11 # Represents audio data more complex than can be represented in a CF_WAVE standard wave + # format. + CF_SYLK = 4 # Microsoft Symbolic Link (SYLK) format. + CF_TEXT = 1 # Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. + # A null character signals the end of the data. Use this format for ANSI text. + CF_TIFF = 6 # Tagged-image file format. + CF_UNICODETEXT = 13 # Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) + # combination. A null character signals the end of the data. + CF_WAVE = 12 # Represents audio data in one of the standard wave formats, such as 11 kHz or + # 22 kHz PCM. + + CF_ALL = [] # If passing an iterable to the paste function, it will retrieve all available formats + + STANDARD_FORMAT_DESCRIPTION = { + # Identifier : ( "Descriptor", encoding), + CF_BITMAP: "BITMAP", + CF_DIB: "DIB", + CF_DIBV5: "DIBV5", + CF_DIF: "DIF", + CF_DSPBITMAP: "DSPBITMAP", + CF_DSPENHMETAFILE: "DSPENHMETAFILE", + CF_DSPMETAFILEPICT: "DSPMETAFILEPICT", + CF_DSPTEXT: "DSPTEXT", + CF_ENHMETAFILE: "ENHMETAFILE", + CF_GDIOBJFIRST: "GDIOBJFIRST", + CF_GDIOBJLAST: "GDIOBJLAST", + CF_HDROP: "HDROP", + CF_LOCALE: "LOCALE", + CF_METAFILEPICT: "METAFILE PICT", + CF_OEMTEXT: "OEM TEXT", + CF_OWNERDISPLAY: "OWNERDISPLAY", + CF_PALETTE: "PALETTE", + CF_PENDATA: "Microsoft PEN DATA", + CF_PRIVATEFIRST: "PRIVATEFIRST", + CF_PRIVATELAST: "PRIVATELAST", + CF_RIFF: "RIFF", + CF_SYLK: "SYLK", + CF_TEXT: "TEXT", + CF_TIFF: "TIFF", + CF_UNICODETEXT: "UNICODE TEXT", + CF_WAVE: "WAVE", + } @contextlib.contextmanager def window(): """ @@ -421,11 +542,14 @@ def clipboard(hwnd): finally: safeCloseClipboard() - def copy_windows(text): + def copy_windows(text_or_dict, clip_format=CF_UNICODETEXT): # This function is heavily based on # http://msdn.com/ms649016#_win32_Copying_Information_to_the_Clipboard - text = _stringifyText(text) # Converts non-str values to str. + if isinstance(text_or_dict, dict): + text_dict = text_or_dict + else: + text_dict = {clip_format: text_or_dict } with window() as hwnd: # http://msdn.com/ms649048 @@ -436,31 +560,80 @@ def copy_windows(text): 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(): + for clip_format, text in text_dict.items(): + if (not PY2) and (not isinstance(text, bytes)): + text = _stringifyText(text) # Converts non-str values to str. + if clip_format not in (CF_UNICODETEXT, ): + text = text.encode(ENCODING) + + 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. + if (not PY2) and isinstance(text, bytes): # This passes in an 8 bit format. + count = len(text) + 1 + handle = safeGlobalAlloc(GMEM_MOVEABLE, + count * sizeof(CHAR)) + locked_handle = safeGlobalLock(handle) + ctypes.memmove(LPCSTR(locked_handle), LPCSTR(text), count * sizeof(CHAR)) + + safeGlobalUnlock(handle) + safeSetClipboardData(clip_format, handle) + else: + 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(clip_format, handle) + + def paste_windows(clip_format=CF_UNICODETEXT): + """If passing a clip format it will get the """ 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 "" - return c_wchar_p(handle).value + if isinstance(clip_format, (list, tuple)): + answer = {} + if len(clip_format)==0: + # Will retrieve the list of available formats + clip_formats = [] + clip_format = safeEnumClipboardFormats(0) + while clip_format: + clip_formats.append(clip_format) + clip_format = safeEnumClipboardFormats(clip_format) + else: + clip_formats = [clip_format, ] # Make it iterable + + for clip_format in clip_formats: + handle = safeGetClipboardData(clip_format) + if not handle: + answer[clip_format] = None + else: + if clip_format in (CF_UNICODETEXT,): + text = c_wchar_p(handle).value + else: + size = safeGlobalSize(handle) + text = string_at(safeGlobalLock(handle), size) + safeGlobalUnlock(handle) + answer[clip_format] = text + # answer[clip_format] = c_wchar_p(handle).value.encode('UTF-16') + return answer + else: + handle = safeGetClipboardData(clip_format) + 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 "" + if clip_format in (CF_UNICODETEXT,): + return c_wchar_p(handle).value + else: + size = safeGlobalSize(handle) + text = string_at(safeGlobalLock(handle), size) + safeGlobalUnlock(handle) + return text return copy_windows, paste_windows @@ -616,7 +789,7 @@ def lazy_load_stub_copy(text): return copy(text) -def lazy_load_stub_paste(): +def lazy_load_stub_paste(make_it_pass_in_the_first_run=None): ''' 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. @@ -635,6 +808,8 @@ def lazy_load_stub_paste(): ''' global copy, paste copy, paste = determine_clipboard() + if make_it_pass_in_the_first_run is not None: + return paste(make_it_pass_in_the_first_run) return paste() From 9d07c338450dd6ac87d6f39974e272e24e1375d8 Mon Sep 17 00:00:00 2001 From: Nuno Brum Date: Thu, 2 Jan 2020 11:19:57 +0100 Subject: [PATCH 3/5] Fixing encoding action to be only on text clipboard formats Reformatting code as per PEP formatting recommendations Adding PyCharm project settings --- .idea/.gitignore | 3 + .idea/dictionaries/nuno_brum.xml | 9 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/pyperclip.iml | 13 + .idea/vcs.xml | 6 + src/pyperclip/__init__.py | 272 +++++++++--------- 8 files changed, 187 insertions(+), 134 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/dictionaries/nuno_brum.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/pyperclip.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..0e40fe8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ + +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/.idea/dictionaries/nuno_brum.xml b/.idea/dictionaries/nuno_brum.xml new file mode 100644 index 0000000..42b8625 --- /dev/null +++ b/.idea/dictionaries/nuno_brum.xml @@ -0,0 +1,9 @@ + + + + pbcopy + pyobjc + pyperclip + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6c993b7 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..90dd044 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pyperclip.iml b/.idea/pyperclip.iml new file mode 100644 index 0000000..d0fbe74 --- /dev/null +++ b/.idea/pyperclip.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/pyperclip/__init__.py b/src/pyperclip/__init__.py index e527623..caa5ed1 100644 --- a/src/pyperclip/__init__.py +++ b/src/pyperclip/__init__.py @@ -56,7 +56,6 @@ from ctypes import c_size_t, sizeof, c_wchar_p, get_errno, c_wchar - # `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. @@ -64,11 +63,12 @@ EXCEPT_MSG = """ Pyperclip could not find a copy/paste mechanism for your system. - For more information, please visit https://pyperclip.readthedocs.io/en/latest/introduction.html#not-implemented-error """ + For more information, please visit: + https://pyperclip.readthedocs.io/en/latest/introduction.html#not-implemented-error """ PY2 = sys.version_info[0] == 2 -STR_OR_UNICODE = unicode if PY2 else str # For paste(): Python 3 uses str, Python 2 uses unicode. +STR_OR_UNICODE = unicode if PY2 else str # For paste(): Python 3 uses str, Python 2 uses unicode. ENCODING = 'utf-8' @@ -78,16 +78,17 @@ else: WHICH_CMD = 'which' + def _executable_exists(name): return subprocess.call([WHICH_CMD, name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0 - # Exceptions class PyperclipException(RuntimeError): pass + class PyperclipWindowsException(PyperclipException): def __init__(self, message): message += " (%s)" % ctypes.WinError() @@ -100,14 +101,14 @@ def _stringifyText(text): else: acceptedTypes = (str, int, float, bool) if not isinstance(text, acceptedTypes): - raise PyperclipException('only str, int, float, and bool values can be copied to the clipboard, not %s' % (text.__class__.__name__)) + raise PyperclipException( + 'only str, int, float, and bool values can be copied to the clipboard, not %s' % text.__class__.__name__) return STR_OR_UNICODE(text) def init_osx_pbcopy_clipboard(): - def copy_osx_pbcopy(text): - text = _stringifyText(text) # Converts non-str values to str. + text = _stringifyText(text) # Converts non-str values to str. p = subprocess.Popen(['pbcopy', 'w'], stdin=subprocess.PIPE, close_fds=True) p.communicate(input=text.encode(ENCODING)) @@ -123,8 +124,8 @@ def paste_osx_pbcopy(): def init_osx_pyobjc_clipboard(): def copy_osx_pyobjc(text): - '''Copy string argument to clipboard''' - text = _stringifyText(text) # Converts non-str values to str. + """Copy string argument to clipboard""" + text = _stringifyText(text) # Converts non-str values to str. newStr = Foundation.NSString.stringWithString_(text).nsstring() newData = newStr.dataUsingEncoding_(Foundation.NSUTF8StringEncoding) board = AppKit.NSPasteboard.generalPasteboard() @@ -132,7 +133,7 @@ def copy_osx_pyobjc(text): board.setData_forType_(newData, AppKit.NSStringPboardType) def paste_osx_pyobjc(): - "Returns contents of clipboard" + """Returns contents of clipboard""" board = AppKit.NSPasteboard.generalPasteboard() content = board.stringForType_(AppKit.NSStringPboardType) return content @@ -146,7 +147,7 @@ def init_gtk_clipboard(): def copy_gtk(text): global cb - text = _stringifyText(text) # Converts non-str values to str. + text = _stringifyText(text) # Converts non-str values to str. cb = gtk.Clipboard() cb.set_text(text) cb.store() @@ -180,7 +181,7 @@ def init_qt_clipboard(): app = QApplication([]) def copy_qt(text): - text = _stringifyText(text) # Converts non-str values to str. + text = _stringifyText(text) # Converts non-str values to str. cb = app.clipboard() cb.setText(text) @@ -192,22 +193,22 @@ def paste_qt(): def init_xclip_clipboard(): - DEFAULT_SELECTION='c' - PRIMARY_SELECTION='p' + DEFAULT_SELECTION = 'c' + PRIMARY_SELECTION = 'p' def copy_xclip(text, primary=False): - text = _stringifyText(text) # Converts non-str values to str. - selection=DEFAULT_SELECTION + text = _stringifyText(text) # Converts non-str values to str. + selection = DEFAULT_SELECTION if primary: - selection=PRIMARY_SELECTION + 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 + selection = DEFAULT_SELECTION if primary: - selection=PRIMARY_SELECTION + selection = PRIMARY_SELECTION p = subprocess.Popen(['xclip', '-selection', selection, '-o'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -220,11 +221,11 @@ def paste_xclip(primary=False): def init_xsel_clipboard(): - DEFAULT_SELECTION='-b' - PRIMARY_SELECTION='-p' + DEFAULT_SELECTION = '-b' + PRIMARY_SELECTION = '-p' def copy_xsel(text, primary=False): - text = _stringifyText(text) # Converts non-str values to str. + text = _stringifyText(text) # Converts non-str values to str. selection_flag = DEFAULT_SELECTION if primary: selection_flag = PRIMARY_SELECTION @@ -246,7 +247,7 @@ def paste_xsel(primary=False): def init_klipper_clipboard(): def copy_klipper(text): - text = _stringifyText(text) # Converts non-str values to str. + text = _stringifyText(text) # Converts non-str values to str. p = subprocess.Popen( ['qdbus', 'org.kde.klipper', '/klipper', 'setClipboardContents', text.encode(ENCODING)], @@ -275,9 +276,10 @@ def paste_klipper(): def init_dev_clipboard_clipboard(): def copy_dev_clipboard(text): - text = _stringifyText(text) # Converts non-str values to str. + text = _stringifyText(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.') + 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.') @@ -310,8 +312,6 @@ def __bool__(self): return ClipboardUnavailable(), ClipboardUnavailable() - - # Windows-related clipboard functions: class CheckedCall(object): def __init__(self, f): @@ -332,7 +332,7 @@ def init_windows_clipboard(): global CF_BITMAP, CF_DIB, CF_DIBV5, CF_DIF, CF_DSPBITMAP, CF_DSPENHMETAFILE, CF_DSPMETAFILEPICT, CF_DSPTEXT global CF_ENHMETAFILE, CF_GDIOBJFIRST, CF_GDIOBJLAST, CF_HDROP, CF_LOCALE, CF_METAFILEPICT, CF_OEMTEXT global CF_OWNERDISPLAY, CF_PALETTE, CF_PENDATA, CF_PRIVATEFIRST, CF_PRIVATELAST, CF_RIFF, CF_SYLK, CF_TEXT - global CF_TIFF, CF_UNICODETEXT, CF_WAVE, CF_ALL + global CF_TIFF, CF_UNICODETEXT, CF_WAVE, CF_ALL, TEXT_FORMATS_NEEDING_ENCONDING from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, HINSTANCE, HMENU, BOOL, UINT, HANDLE, CHAR) @@ -400,103 +400,102 @@ def init_windows_clipboard(): GMEM_MOVEABLE = 0x0002 + # Standard Clipboard Formats in Windows # Constant = value # Description - CF_BITMAP = 2 # A handle to a bitmap (HBITMAP). - CF_DIB = 8 # A memory object containing a BITMAPINFO structure followed by the bitmap bits. - CF_DIBV5 = 17 # A memory object containing a BITMAPV5HEADER structure followed by the bitmap color - # space information and the bitmap bits. - CF_DIF = 5 # Software Arts' Data Interchange Format. - CF_DSPBITMAP = 0x0082 # Bitmap display format associated with a private format. The hMem parameter must be a - # handle to data that can be displayed in bitmap format in lieu of the privately - # formatted data. + CF_BITMAP = 2 # A handle to a bitmap (HBITMAP). + CF_DIB = 8 # A memory object containing a BITMAPINFO structure followed by the bitmap bits. + CF_DIBV5 = 17 # A memory object containing a BITMAPV5HEADER structure followed by the bitmap color + # space information and the bitmap bits. + CF_DIF = 5 # Software Arts' Data Interchange Format. + CF_DSPBITMAP = 0x0082 # Bitmap display format associated with a private format. The hMem parameter must be a + # handle to data that can be displayed in bitmap format in lieu of the privately + # formatted data. CF_DSPENHMETAFILE = 0x008E # Enhanced metafile display format associated with a private format. - # The hMem parameter must be a handle to data that can be displayed in enhanced metafile - # format in lieu of the privately formatted data. - CF_DSPMETAFILEPICT = 0x0083 # Metafile-picture display format associated with a private format. The hMem parameter - # must be a handle to data that can be displayed in metafile-picture format in lieu of - # the privately formatted data. - CF_DSPTEXT = 0x0081 # Text display format associated with a private format. The hMem parameter must be a - # handle to data that can be displayed in text format in lieu of the privately formatted - # data. - CF_ENHMETAFILE = 14 # A handle to an enhanced metafile (HENHMETAFILE). - CF_GDIOBJFIRST = 0x0300 # Start of a range of integer values for application-defined GDI object clipboard - # formats. The end of the range is CF_GDIOBJLAST. - # Handles associated with clipboard formats in this range are not automatically deleted - # using the GlobalFree function when the clipboard is emptied. Also, when using values - # in this range, the hMem parameter is not a handle to a GDI object, but is a handle - # allocated by the GlobalAlloc function with the GMEM_MOVEABLE flag. - CF_GDIOBJLAST = 0x03FF # See CF_GDIOBJFIRST. - CF_HDROP = 15 # A handle to type HDROP that identifies a list of files. An application can retrieve - # information about the files by passing the handle to the DragQueryFile function. - CF_LOCALE = 16 # The data is a handle to the locale identifier associated with text in the clipboard. - # When you close the clipboard, if it contains CF_TEXT data but no CF_LOCALE data, the - # system automatically sets the CF_LOCALE format to the current input language. You can - # use the CF_LOCALE format to associate a different locale with the clipboard text. - # An application that pastes text from the clipboard can retrieve this format to - # determine which character set was used to generate the text. - # Note that the clipboard does not support plain text in multiple character sets. - # To achieve this, use a formatted text data type such as RTF instead. - # The system uses the code page associated with CF_LOCALE to implicitly convert from - # CF_TEXT to CF_UNICODETEXT. Therefore, the correct code page table is used for the - # conversion. - CF_METAFILEPICT = 3 # Handle to a metafile picture format as defined by the METAFILEPICT structure. When - # passing a CF_METAFILEPICT handle by means of DDE, the application responsible for - # deleting hMem should also free the metafile referred to by the CF_METAFILEPICT handle. - CF_OEMTEXT = 7 # Text format containing characters in the OEM character set. Each line ends with a - # carriage return/linefeed (CR-LF) combination. A null character signals the end of the - # data. - CF_OWNERDISPLAY = 0x0080 # Owner-display format. The clipboard owner must display and update the clipboard viewer - # window, and receive the WM_ASKCBFORMATNAME, WM_HSCROLLCLIPBOARD, WM_PAINTCLIPBOARD, - # WM_SIZECLIPBOARD, and WM_VSCROLLCLIPBOARD messages. The hMem parameter must be NULL. - CF_PALETTE = 9 # Handle to a color palette. Whenever an application places data in the clipboard that - # depends on or assumes a color palette, it should place the palette on the clipboard as - # well. - # If the clipboard contains data in the CF_PALETTE (logical color palette) format, the - # application should use the SelectPalette and RealizePalette functions to realize - # (compare) any other data in the clipboard against that logical palette. - # When displaying clipboard data, the clipboard always uses as its current palette any - # object on the clipboard that is in the CF_PALETTE format. - CF_PENDATA = 10 # Data for the pen extensions to the Microsoft Windows for Pen Computing. - CF_PRIVATEFIRST = 0x0200 # Start of a range of integer values for private clipboard formats. The range ends with - # CF_PRIVATELAST. Handles associated with private clipboard formats are not freed - # automatically; the clipboard owner must free such handles, typically in response to - # the WM_DESTROYCLIPBOARD message. - CF_PRIVATELAST = 0x02FF # See CF_PRIVATEFIRST. - CF_RIFF = 11 # Represents audio data more complex than can be represented in a CF_WAVE standard wave - # format. - CF_SYLK = 4 # Microsoft Symbolic Link (SYLK) format. - CF_TEXT = 1 # Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. - # A null character signals the end of the data. Use this format for ANSI text. - CF_TIFF = 6 # Tagged-image file format. - CF_UNICODETEXT = 13 # Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) - # combination. A null character signals the end of the data. - CF_WAVE = 12 # Represents audio data in one of the standard wave formats, such as 11 kHz or - # 22 kHz PCM. - - CF_ALL = [] # If passing an iterable to the paste function, it will retrieve all available formats + # The hMem parameter must be a handle to data that can be displayed in enhanced metafile + # format in lieu of the privately formatted data. + CF_DSPMETAFILEPICT = 0x0083 # Metafile-picture display format associated with a private format. The hMem parameter + # must be a handle to data that can be displayed in metafile-picture format in lieu of + # the privately formatted data. + CF_DSPTEXT = 0x0081 # Text display format associated with a private format. The hMem parameter must be a + # handle to data that can be displayed in text format in lieu of the privately formatted data. + CF_ENHMETAFILE = 14 # A handle to an enhanced metafile (HENHMETAFILE). + CF_GDIOBJFIRST = 0x0300 # Start of a range of integer values for application-defined GDI object clipboard + # formats. The end of the range is CF_GDIOBJLAST. + # Handles associated with clipboard formats in this range are not automatically deleted + # using the GlobalFree function when the clipboard is emptied. Also, when using values + # in this range, the hMem parameter is not a handle to a GDI object, but is a handle + # allocated by the GlobalAlloc function with the GMEM_MOVEABLE flag. + CF_GDIOBJLAST = 0x03FF # See CF_GDIOBJFIRST. + CF_HDROP = 15 # A handle to type HDROP that identifies a list of files. An application can retrieve + # information about the files by passing the handle to the DragQueryFile function. + CF_LOCALE = 16 # The data is a handle to the locale identifier associated with text in the clipboard. + # When you close the clipboard, if it contains CF_TEXT data but no CF_LOCALE data, the + # system automatically sets the CF_LOCALE format to the current input language. You can + # use the CF_LOCALE format to associate a different locale with the clipboard text. + # An application that pastes text from the clipboard can retrieve this format to + # determine which character set was used to generate the text. + # Note that the clipboard does not support plain text in multiple character sets. + # To achieve this, use a formatted text data type such as RTF instead. + # The system uses the code page associated with CF_LOCALE to implicitly convert from + # CF_TEXT to CF_UNICODETEXT. Therefore, the correct code page table is used for the + # conversion. + CF_METAFILEPICT = 3 # Handle to a metafile picture format as defined by the METAFILEPICT structure. When + # passing a CF_METAFILEPICT handle by means of DDE, the application responsible for + # deleting hMem should also free the metafile referred to by the CF_METAFILEPICT handle. + CF_OEMTEXT = 7 # Text format containing characters in the OEM character set. Each line ends with a + # carriage return/linefeed (CR-LF) combination. A null character signals the end of the + # data. + CF_OWNERDISPLAY = 0x0080 # Owner-display format. The clipboard owner must display and update the clipboard viewer + # window, and receive the WM_ASKCBFORMATNAME, WM_HSCROLLCLIPBOARD, WM_PAINTCLIPBOARD, + # WM_SIZECLIPBOARD, and WM_VSCROLLCLIPBOARD messages. The hMem parameter must be NULL. + CF_PALETTE = 9 # Handle to a color palette. Whenever an application places data in the clipboard that + # depends on or assumes a color palette, it should place the palette on the clipboard as + # well. + # If the clipboard contains data in the CF_PALETTE (logical color palette) format, the + # application should use the SelectPalette and RealizePalette functions to realize + # (compare) any other data in the clipboard against that logical palette. + # When displaying clipboard data, the clipboard always uses as its current palette any + # object on the clipboard that is in the CF_PALETTE format. + CF_PENDATA = 10 # Data for the pen extensions to the Microsoft Windows for Pen Computing. + CF_PRIVATEFIRST = 0x0200 # Start of a range of integer values for private clipboard formats. The range ends with + # CF_PRIVATELAST. Handles associated with private clipboard formats are not freed + # automatically; the clipboard owner must free such handles, typically in response to + # the WM_DESTROYCLIPBOARD message. + CF_PRIVATELAST = 0x02FF # See CF_PRIVATEFIRST. + CF_RIFF = 11 # Represents audio data more complex than can be represented in a CF_WAVE standard wave format. + CF_SYLK = 4 # Microsoft Symbolic Link (SYLK) format. + CF_TEXT = 1 # Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. + # A null character signals the end of the data. Use this format for ANSI text. + CF_TIFF = 6 # Tagged-image file format. + CF_UNICODETEXT = 13 # Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) + # combination. A null character signals the end of the data. + CF_WAVE = 12 # Represents audio data in one of the standard wave formats, such as 11 kHz or + # 22 kHz PCM. + + CF_ALL = [] # If passing an iterable to the paste function, it will retrieve all available formats STANDARD_FORMAT_DESCRIPTION = { - # Identifier : ( "Descriptor", encoding), + # Identifier: "Descriptor", CF_BITMAP: "BITMAP", CF_DIB: "DIB", CF_DIBV5: "DIBV5", CF_DIF: "DIF", - CF_DSPBITMAP: "DSPBITMAP", - CF_DSPENHMETAFILE: "DSPENHMETAFILE", - CF_DSPMETAFILEPICT: "DSPMETAFILEPICT", - CF_DSPTEXT: "DSPTEXT", + CF_DSPBITMAP: "DSP BITMAP", + CF_DSPENHMETAFILE: "DSP ENHMETAFILE", + CF_DSPMETAFILEPICT: "DSP METAFILEPICT", + CF_DSPTEXT: "DSP TEXT", CF_ENHMETAFILE: "ENHMETAFILE", - CF_GDIOBJFIRST: "GDIOBJFIRST", - CF_GDIOBJLAST: "GDIOBJLAST", - CF_HDROP: "HDROP", + CF_GDIOBJFIRST: "GDIOBJ FIRST", + CF_GDIOBJLAST: "GDIOBJ LAST", + CF_HDROP: "Handle Drag and DROP", CF_LOCALE: "LOCALE", CF_METAFILEPICT: "METAFILE PICT", CF_OEMTEXT: "OEM TEXT", - CF_OWNERDISPLAY: "OWNERDISPLAY", + CF_OWNERDISPLAY: "OWNER DISPLAY", CF_PALETTE: "PALETTE", CF_PENDATA: "Microsoft PEN DATA", - CF_PRIVATEFIRST: "PRIVATEFIRST", - CF_PRIVATELAST: "PRIVATELAST", + CF_PRIVATEFIRST: "PRIVATE FIRST", + CF_PRIVATELAST: "PRIVATE LAST", CF_RIFF: "RIFF", CF_SYLK: "SYLK", CF_TEXT: "TEXT", @@ -504,6 +503,8 @@ def init_windows_clipboard(): CF_UNICODETEXT: "UNICODE TEXT", CF_WAVE: "WAVE", } + TEXT_FORMATS_NEEDING_ENCONDING = (CF_TEXT, CF_DSPTEXT) + @contextlib.contextmanager def window(): """ @@ -549,7 +550,7 @@ def copy_windows(text_or_dict, clip_format=CF_UNICODETEXT): if isinstance(text_or_dict, dict): text_dict = text_or_dict else: - text_dict = {clip_format: text_or_dict } + text_dict = {clip_format: text_or_dict} with window() as hwnd: # http://msdn.com/ms649048 @@ -563,7 +564,7 @@ def copy_windows(text_or_dict, clip_format=CF_UNICODETEXT): for clip_format, text in text_dict.items(): if (not PY2) and (not isinstance(text, bytes)): text = _stringifyText(text) # Converts non-str values to str. - if clip_format not in (CF_UNICODETEXT, ): + if clip_format in TEXT_FORMATS_NEEDING_ENCONDING: text = text.encode(ENCODING) if text: @@ -574,7 +575,7 @@ def copy_windows(text_or_dict, clip_format=CF_UNICODETEXT): if (not PY2) and isinstance(text, bytes): # This passes in an 8 bit format. count = len(text) + 1 handle = safeGlobalAlloc(GMEM_MOVEABLE, - count * sizeof(CHAR)) + count * sizeof(CHAR)) locked_handle = safeGlobalLock(handle) ctypes.memmove(LPCSTR(locked_handle), LPCSTR(text), count * sizeof(CHAR)) @@ -595,7 +596,7 @@ def paste_windows(clip_format=CF_UNICODETEXT): with clipboard(None): if isinstance(clip_format, (list, tuple)): answer = {} - if len(clip_format)==0: + if len(clip_format) == 0: # Will retrieve the list of available formats clip_formats = [] clip_format = safeEnumClipboardFormats(0) @@ -616,6 +617,8 @@ def paste_windows(clip_format=CF_UNICODETEXT): size = safeGlobalSize(handle) text = string_at(safeGlobalLock(handle), size) safeGlobalUnlock(handle) + if clip_format in TEXT_FORMATS_NEEDING_ENCONDING: + text.decode(ENCODING) answer[clip_format] = text # answer[clip_format] = c_wchar_p(handle).value.encode('UTF-16') return answer @@ -633,6 +636,8 @@ def paste_windows(clip_format=CF_UNICODETEXT): size = safeGlobalSize(handle) text = string_at(safeGlobalLock(handle), size) safeGlobalUnlock(handle) + if clip_format in TEXT_FORMATS_NEEDING_ENCONDING: + text.decode(ENCODING) return text return copy_windows, paste_windows @@ -640,7 +645,7 @@ def paste_windows(clip_format=CF_UNICODETEXT): def init_wsl_clipboard(): def copy_wsl(text): - text = _stringifyText(text) # Converts non-str values to str. + text = _stringifyText(text) # Converts non-str values to str. p = subprocess.Popen(['clip.exe'], stdin=subprocess.PIPE, close_fds=True) p.communicate(input=text.encode(ENCODING)) @@ -659,19 +664,21 @@ def paste_wsl(): # Automatic detection of clipboard mechanisms and importing is done in deteremine_clipboard(): def determine_clipboard(): - ''' + """ Determine the OS/platform and set the copy() and paste() functions accordingly. - ''' + """ global Foundation, AppKit, gtk, qtpy, PyQt4, 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' + 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') + 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: @@ -698,7 +705,7 @@ def determine_clipboard(): try: import gtk # check if gtk is installed except ImportError: - pass # We want to fail fast for all non-ImportError exceptions. + pass # We want to fail fast for all non-ImportError exceptions. else: return init_gtk_clipboard() @@ -710,7 +717,8 @@ def determine_clipboard(): 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. + # 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 except ImportError: @@ -721,7 +729,7 @@ def determine_clipboard(): try: import PyQt4 # check if PyQt4 is installed except ImportError: - pass # We want to fail fast for all non-ImportError exceptions. + pass # We want to fail fast for all non-ImportError exceptions. else: return init_qt_clipboard() else: @@ -729,12 +737,11 @@ def determine_clipboard(): else: return init_qt_clipboard() - 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: @@ -747,13 +754,13 @@ def set_clipboard(clipboard): - 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, 'gtk': init_gtk_clipboard, - 'qt': init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5' + 'qt': init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5' 'xclip': init_xclip_clipboard, 'xsel': init_xsel_clipboard, 'klipper': init_klipper_clipboard, @@ -768,7 +775,7 @@ def set_clipboard(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. @@ -783,14 +790,14 @@ def lazy_load_stub_copy(text): 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(make_it_pass_in_the_first_run=None): - ''' + """ 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. @@ -805,7 +812,7 @@ def lazy_load_stub_paste(make_it_pass_in_the_first_run=None): 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() if make_it_pass_in_the_first_run is not None: @@ -822,7 +829,4 @@ def is_available(): # 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'] - - From 17793cc55c4c87d146cd87d564c5bf1f785f7de6 Mon Sep 17 00:00:00 2001 From: Nuno Brum Date: Sat, 4 Jan 2020 15:01:40 +0100 Subject: [PATCH 4/5] Adding the make_it_pass_in_the_first_run to the copy command as well. --- src/pyperclip/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pyperclip/__init__.py b/src/pyperclip/__init__.py index caa5ed1..01ecfc7 100644 --- a/src/pyperclip/__init__.py +++ b/src/pyperclip/__init__.py @@ -774,7 +774,7 @@ def set_clipboard(clipboard): copy, paste = clipboard_types[clipboard]() -def lazy_load_stub_copy(text): +def lazy_load_stub_copy(text, make_it_pass_in_the_first_run=None): """ 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. @@ -793,6 +793,8 @@ def lazy_load_stub_copy(text): """ global copy, paste copy, paste = determine_clipboard() + if make_it_pass_in_the_first_run is not None: + return copy(text, make_it_pass_in_the_first_run) return copy(text) From bfb8b410cd51fd62b6741c5a6636f9e6c5dc7a00 Mon Sep 17 00:00:00 2001 From: nunobrum Date: Sat, 5 Mar 2022 18:35:26 +0100 Subject: [PATCH 5/5] Integrating last version from original Al Sweigart repository --- .idea/misc.xml | 2 +- .idea/pyperclip.iml | 2 +- src/pyperclip/__init__.py | 263 +++++++++++++++++++++++++------------- 3 files changed, 175 insertions(+), 92 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 6c993b7..ed0b301 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/.idea/pyperclip.iml b/.idea/pyperclip.iml index d0fbe74..916ce74 100644 --- a/.idea/pyperclip.iml +++ b/.idea/pyperclip.iml @@ -4,7 +4,7 @@ - + diff --git a/src/pyperclip/__init__.py b/src/pyperclip/__init__.py index 01ecfc7..1d62b81 100644 --- a/src/pyperclip/__init__.py +++ b/src/pyperclip/__init__.py @@ -16,9 +16,11 @@ 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 or xsel via package manager. For example, in Debian: +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 gtk or PyQt5/PyQt4 modules installed. @@ -37,13 +39,14 @@ - 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.7.0' +__version__ = '1.8.2' import contextlib import ctypes @@ -72,18 +75,19 @@ ENCODING = 'utf-8' -# The "which" unix command finds where a command is. -if platform.system() == 'Windows': - WHICH_CMD = 'where' -else: - WHICH_CMD = 'which' - +try: + from shutil import which as _executable_exists +except ImportError: + # The "which" unix command finds where a command is. + if platform.system() == 'Windows': + WHICH_CMD = 'where' + else: + WHICH_CMD = 'which' def _executable_exists(name): return subprocess.call([WHICH_CMD, name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0 - # Exceptions class PyperclipException(RuntimeError): pass @@ -94,6 +98,8 @@ def __init__(self, message): message += " (%s)" % ctypes.WinError() super(PyperclipWindowsException, self).__init__(message) +class PyperclipTimeoutException(PyperclipException): + pass def _stringifyText(text): if PY2: @@ -245,6 +251,33 @@ def paste_xsel(primary=False): return copy_xsel, paste_xsel +def init_wl_clipboard(): + PRIMARY_SELECTION = "-p" + + def copy_wl(text, primary=False): + text = _stringifyText(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"] + if primary: + args.append(PRIMARY_SELECTION) + p = subprocess.Popen(args, stdout=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 = _stringifyText(text) # Converts non-str values to str. @@ -402,75 +435,77 @@ def init_windows_clipboard(): # Standard Clipboard Formats in Windows # Constant = value # Description - CF_BITMAP = 2 # A handle to a bitmap (HBITMAP). - CF_DIB = 8 # A memory object containing a BITMAPINFO structure followed by the bitmap bits. - CF_DIBV5 = 17 # A memory object containing a BITMAPV5HEADER structure followed by the bitmap color - # space information and the bitmap bits. - CF_DIF = 5 # Software Arts' Data Interchange Format. - CF_DSPBITMAP = 0x0082 # Bitmap display format associated with a private format. The hMem parameter must be a - # handle to data that can be displayed in bitmap format in lieu of the privately - # formatted data. + CF_BITMAP = 2 # A handle to a bitmap (HBITMAP). + CF_DIB = 8 # A memory object containing a BITMAPINFO structure followed by the bitmap bits. + CF_DIBV5 = 17 # A memory object containing a BITMAPV5HEADER structure followed by the bitmap color + # space information and the bitmap bits. + CF_DIF = 5 # Software Arts' Data Interchange Format. + CF_DSPBITMAP = 0x0082 # Bitmap display format associated with a private format. The hMem parameter must be a + # handle to data that can be displayed in bitmap format in lieu of the privately + # formatted data. CF_DSPENHMETAFILE = 0x008E # Enhanced metafile display format associated with a private format. - # The hMem parameter must be a handle to data that can be displayed in enhanced metafile - # format in lieu of the privately formatted data. - CF_DSPMETAFILEPICT = 0x0083 # Metafile-picture display format associated with a private format. The hMem parameter - # must be a handle to data that can be displayed in metafile-picture format in lieu of - # the privately formatted data. - CF_DSPTEXT = 0x0081 # Text display format associated with a private format. The hMem parameter must be a - # handle to data that can be displayed in text format in lieu of the privately formatted data. - CF_ENHMETAFILE = 14 # A handle to an enhanced metafile (HENHMETAFILE). - CF_GDIOBJFIRST = 0x0300 # Start of a range of integer values for application-defined GDI object clipboard - # formats. The end of the range is CF_GDIOBJLAST. - # Handles associated with clipboard formats in this range are not automatically deleted - # using the GlobalFree function when the clipboard is emptied. Also, when using values - # in this range, the hMem parameter is not a handle to a GDI object, but is a handle - # allocated by the GlobalAlloc function with the GMEM_MOVEABLE flag. - CF_GDIOBJLAST = 0x03FF # See CF_GDIOBJFIRST. - CF_HDROP = 15 # A handle to type HDROP that identifies a list of files. An application can retrieve - # information about the files by passing the handle to the DragQueryFile function. - CF_LOCALE = 16 # The data is a handle to the locale identifier associated with text in the clipboard. - # When you close the clipboard, if it contains CF_TEXT data but no CF_LOCALE data, the - # system automatically sets the CF_LOCALE format to the current input language. You can - # use the CF_LOCALE format to associate a different locale with the clipboard text. - # An application that pastes text from the clipboard can retrieve this format to - # determine which character set was used to generate the text. - # Note that the clipboard does not support plain text in multiple character sets. - # To achieve this, use a formatted text data type such as RTF instead. - # The system uses the code page associated with CF_LOCALE to implicitly convert from - # CF_TEXT to CF_UNICODETEXT. Therefore, the correct code page table is used for the - # conversion. - CF_METAFILEPICT = 3 # Handle to a metafile picture format as defined by the METAFILEPICT structure. When - # passing a CF_METAFILEPICT handle by means of DDE, the application responsible for - # deleting hMem should also free the metafile referred to by the CF_METAFILEPICT handle. - CF_OEMTEXT = 7 # Text format containing characters in the OEM character set. Each line ends with a - # carriage return/linefeed (CR-LF) combination. A null character signals the end of the - # data. - CF_OWNERDISPLAY = 0x0080 # Owner-display format. The clipboard owner must display and update the clipboard viewer - # window, and receive the WM_ASKCBFORMATNAME, WM_HSCROLLCLIPBOARD, WM_PAINTCLIPBOARD, - # WM_SIZECLIPBOARD, and WM_VSCROLLCLIPBOARD messages. The hMem parameter must be NULL. - CF_PALETTE = 9 # Handle to a color palette. Whenever an application places data in the clipboard that - # depends on or assumes a color palette, it should place the palette on the clipboard as - # well. - # If the clipboard contains data in the CF_PALETTE (logical color palette) format, the - # application should use the SelectPalette and RealizePalette functions to realize - # (compare) any other data in the clipboard against that logical palette. - # When displaying clipboard data, the clipboard always uses as its current palette any - # object on the clipboard that is in the CF_PALETTE format. - CF_PENDATA = 10 # Data for the pen extensions to the Microsoft Windows for Pen Computing. - CF_PRIVATEFIRST = 0x0200 # Start of a range of integer values for private clipboard formats. The range ends with - # CF_PRIVATELAST. Handles associated with private clipboard formats are not freed - # automatically; the clipboard owner must free such handles, typically in response to - # the WM_DESTROYCLIPBOARD message. - CF_PRIVATELAST = 0x02FF # See CF_PRIVATEFIRST. - CF_RIFF = 11 # Represents audio data more complex than can be represented in a CF_WAVE standard wave format. - CF_SYLK = 4 # Microsoft Symbolic Link (SYLK) format. - CF_TEXT = 1 # Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. - # A null character signals the end of the data. Use this format for ANSI text. - CF_TIFF = 6 # Tagged-image file format. - CF_UNICODETEXT = 13 # Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) - # combination. A null character signals the end of the data. - CF_WAVE = 12 # Represents audio data in one of the standard wave formats, such as 11 kHz or - # 22 kHz PCM. + # The hMem parameter must be a handle to data that can be displayed in enhanced metafile + # format in lieu of the privately formatted data. + CF_DSPMETAFILEPICT = 0x0083 # Metafile-picture display format associated with a private format. The hMem parameter + # must be a handle to data that can be displayed in metafile-picture format in lieu of + # the privately formatted data. + CF_DSPTEXT = 0x0081 # Text display format associated with a private format. The hMem parameter must be a + # handle to data that can be displayed in text format in lieu of the privately formatted + # data. + CF_ENHMETAFILE = 14 # A handle to an enhanced metafile (HENHMETAFILE). + CF_GDIOBJFIRST = 0x0300 # Start of a range of integer values for application-defined GDI object clipboard + # formats. The end of the range is CF_GDIOBJLAST. + # Handles associated with clipboard formats in this range are not automatically deleted + # using the GlobalFree function when the clipboard is emptied. Also, when using values + # in this range, the hMem parameter is not a handle to a GDI object, but is a handle + # allocated by the GlobalAlloc function with the GMEM_MOVEABLE flag. + CF_GDIOBJLAST = 0x03FF # See CF_GDIOBJFIRST. + CF_HDROP = 15 # A handle to type HDROP that identifies a list of files. An application can retrieve + # information about the files by passing the handle to the DragQueryFile function. + CF_LOCALE = 16 # The data is a handle to the locale identifier associated with text in the clipboard. + # When you close the clipboard, if it contains CF_TEXT data but no CF_LOCALE data, the + # system automatically sets the CF_LOCALE format to the current input language. You can + # use the CF_LOCALE format to associate a different locale with the clipboard text. + # An application that pastes text from the clipboard can retrieve this format to + # determine which character set was used to generate the text. + # Note that the clipboard does not support plain text in multiple character sets. + # To achieve this, use a formatted text data type such as RTF instead. + # The system uses the code page associated with CF_LOCALE to implicitly convert from + # CF_TEXT to CF_UNICODETEXT. Therefore, the correct code page table is used for the + # conversion. + CF_METAFILEPICT = 3 # Handle to a metafile picture format as defined by the METAFILEPICT structure. When + # passing a CF_METAFILEPICT handle by means of DDE, the application responsible for + # deleting hMem should also free the metafile referred to by the CF_METAFILEPICT handle. + CF_OEMTEXT = 7 # Text format containing characters in the OEM character set. Each line ends with a + # carriage return/linefeed (CR-LF) combination. A null character signals the end of the + # data. + CF_OWNERDISPLAY = 0x0080 # Owner-display format. The clipboard owner must display and update the clipboard viewer + # window, and receive the WM_ASKCBFORMATNAME, WM_HSCROLLCLIPBOARD, WM_PAINTCLIPBOARD, + # WM_SIZECLIPBOARD, and WM_VSCROLLCLIPBOARD messages. The hMem parameter must be NULL. + CF_PALETTE = 9 # Handle to a color palette. Whenever an application places data in the clipboard that + # depends on or assumes a color palette, it should place the palette on the clipboard as + # well. + # If the clipboard contains data in the CF_PALETTE (logical color palette) format, the + # application should use the SelectPalette and RealizePalette functions to realize + # (compare) any other data in the clipboard against that logical palette. + # When displaying clipboard data, the clipboard always uses as its current palette any + # object on the clipboard that is in the CF_PALETTE format. + CF_PENDATA = 10 # Data for the pen extensions to the Microsoft Windows for Pen Computing. + CF_PRIVATEFIRST = 0x0200 # Start of a range of integer values for private clipboard formats. The range ends with + # CF_PRIVATELAST. Handles associated with private clipboard formats are not freed + # automatically; the clipboard owner must free such handles, typically in response to + # the WM_DESTROYCLIPBOARD message. + CF_PRIVATELAST = 0x02FF # See CF_PRIVATEFIRST. + CF_RIFF = 11 # Represents audio data more complex than can be represented in a CF_WAVE standard wave + # format. + CF_SYLK = 4 # Microsoft Symbolic Link (SYLK) format. + CF_TEXT = 1 # Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. + # A null character signals the end of the data. Use this format for ANSI text. + CF_TIFF = 6 # Tagged-image file format. + CF_UNICODETEXT = 13 # Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) + # combination. A null character signals the end of the data. + CF_WAVE = 12 # Represents audio data in one of the standard wave formats, such as 11 kHz or + # 22 kHz PCM. CF_ALL = [] # If passing an iterable to the paste function, it will retrieve all available formats @@ -651,7 +686,8 @@ def copy_wsl(text): p.communicate(input=text.encode(ENCODING)) def paste_wsl(): - p = subprocess.Popen(['powershell.exe', '-command', 'Get-Clipboard'], + # '-noprofile' speeds up load time + p = subprocess.Popen(['powershell.exe', '-noprofile', '-command', 'Get-Clipboard'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) @@ -685,9 +721,9 @@ def determine_clipboard(): elif os.name == 'nt' or platform.system() == 'Windows': return init_windows_clipboard() - if platform.system() == 'Linux': + if platform.system() == 'Linux' and os.path.isfile('/proc/version'): with open('/proc/version', 'r') as f: - if "Microsoft" in f.read(): + if "microsoft" in f.read().lower(): return init_wsl_clipboard() # Setup for the MAC OS X platform: @@ -709,6 +745,11 @@ def determine_clipboard(): else: return init_gtk_clipboard() + if ( + os.environ.get("WAYLAND_DISPLAY") and + _executable_exists("wl-copy") + ): + return init_wl_clipboard() if _executable_exists("xsel"): return init_xsel_clipboard() if _executable_exists("xclip"): @@ -757,15 +798,18 @@ def set_clipboard(clipboard): """ global copy, paste - clipboard_types = {'pbcopy': init_osx_pbcopy_clipboard, - 'pyobjc': init_osx_pyobjc_clipboard, - 'gtk': init_gtk_clipboard, - 'qt': init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5' - 'xclip': init_xclip_clipboard, - 'xsel': init_xsel_clipboard, - 'klipper': init_klipper_clipboard, - 'windows': init_windows_clipboard, - 'no': init_no_clipboard} + clipboard_types = { + "pbcopy": init_osx_pbcopy_clipboard, + "pyobjc": init_osx_pyobjc_clipboard, + "gtk": init_gtk_clipboard, + "qt": init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', 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()]))) @@ -831,4 +875,43 @@ def is_available(): # 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'] + +def waitForPaste(timeout=None): + """This function call blocks until a non-empty text string exists on the + clipboard. It returns this text. + + This function raises PyperclipTimeoutException if timeout was set to + a number of seconds that has elapsed without non-empty text being put on + the clipboard.""" + startTime = time.time() + while True: + clipboardText = paste() + if clipboardText != '': + return clipboardText + time.sleep(0.01) + + if timeout is not None and time.time() > startTime + timeout: + raise PyperclipTimeoutException('waitForPaste() timed out after ' + str(timeout) + ' seconds.') + + +def waitForNewPaste(timeout=None): + """This function call blocks until a new text string exists on the + clipboard that is different from the text that was there when the function + was first called. It returns this text. + + This function raises PyperclipTimeoutException if timeout was set to + a number of seconds that has elapsed without non-empty text being put on + the clipboard.""" + startTime = time.time() + originalText = paste() + while True: + currentText = paste() + if currentText != originalText: + return currentText + time.sleep(0.01) + + if timeout is not None and time.time() > startTime + timeout: + raise PyperclipTimeoutException('waitForNewPaste() timed out after ' + str(timeout) + ' seconds.') + + +__all__ = ['copy', 'paste', 'waitForPaste', 'waitForNewPaste', 'set_clipboard', 'determine_clipboard']