diff --git a/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py index 806d42381afc6..e411665c3f15f 100644 --- a/pandas/io/clipboard/__init__.py +++ b/pandas/io/clipboard/__init__.py @@ -87,12 +87,7 @@ def _executable_exists(name): - return ( - subprocess.call( - [WHICH_CMD, name], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - == 0 - ) + return which(name) is not None def _stringifyText(text) -> str: @@ -405,274 +400,4 @@ 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 = _stringifyText(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 "" - return c_wchar_p(handle).value - - return copy_windows, paste_windows - - -def init_wsl_clipboard(): - def copy_wsl(text): - text = _stringifyText(text) # Converts non-str values to str. - with subprocess.Popen(["clip.exe"], stdin=subprocess.PIPE, close_fds=True) as p: - p.communicate(input=text.encode(ENCODING)) - - def paste_wsl(): - with subprocess.Popen( - ["powershell.exe", "-command", "Get-Clipboard"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True, - ) as p: - stdout = p.communicate()[0] - # WSL appends "\r\n" to the contents. - return stdout[:-2].decode(ENCODING) - - 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, 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' - # FIXME(pyperclip#55): 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", - stacklevel=find_stack_level(), - ) - 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": - if which("wslconfig.exe"): - return init_wsl_clipboard() - - # Setup for the macOS platform: - if os.name == "mac" or platform.system() == "Darwin": - try: - import AppKit - import Foundation # check if pyobjc is installed - except ImportError: - return init_osx_pbcopy_clipboard() - else: - return init_osx_pyobjc_clipboard() - - # Setup for the LINUX platform: - if HAS_DISPLAY: - if _executable_exists("xsel"): - return init_xsel_clipboard() - if _executable_exists("xclip"): - return init_xclip_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/project/QtPy - import qtpy # check if qtpy is installed - except ImportError: - # If qtpy isn't installed, fall back on importing PyQt4. - try: - import PyQt5 # check if PyQt5 is installed - except ImportError: - try: - import PyQt4 # check if PyQt4 is installed - except ImportError: - pass # We want to fail fast for all non-ImportError exceptions. - else: - return init_qt_clipboard() - else: - return init_qt_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: - - pbcopy - - pyobjc (default on macOS) - - 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', 'pyqt4', and 'pyqt5' - "xclip": init_xclip_clipboard, - "xsel": init_xsel_clipboard, - "klipper": init_klipper_clipboard, - "windows": init_windows_clipboard, - "no": init_no_clipboard, - } - - if clipboard not in clipboard_types: - allowed_clipboard_types = [repr(_) for _ in clipboard_types] - raise ValueError( - f"Argument must be one of {', '.join(allowed_clipboard_types)}" - ) - - # 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 PyQt4 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 PyQt4 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() -> bool: - 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"] - -# pandas aliases -clipboard_get = paste -clipboard_set = copy + # as predefined \ No newline at end of file