From 75263101652d4423786b85618c1718bd66e5a672 Mon Sep 17 00:00:00 2001 From: Kostis Anagnostopoulos Date: Thu, 22 Aug 2019 22:24:41 +0300 Subject: [PATCH 1/2] enh: reuse standard-lib's `which` util, no subproc --- src/pyperclip/__init__.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/pyperclip/__init__.py b/src/pyperclip/__init__.py index 3c2ea6c..0073046 100644 --- a/src/pyperclip/__init__.py +++ b/src/pyperclip/__init__.py @@ -72,15 +72,18 @@ ENCODING = 'utf-8' -# 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 +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 From 3f6b5daf5847f36c9c713d246f7b0bbcc9eb7115 Mon Sep 17 00:00:00 2001 From: Kostis Anagnostopoulos Date: Thu, 22 Aug 2019 22:31:20 +0300 Subject: [PATCH 2/2] FEAT: WAYLAND support with wl-copy/wl-paste cmds --- src/pyperclip/__init__.py | 58 ++++++++++++++++++++++++++++++++------- tests/test_pyperclip.py | 6 ++++ 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/pyperclip/__init__.py b/src/pyperclip/__init__.py index 0073046..ceb6678 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,6 +39,7 @@ - pbpaste - xclip - xsel + - wl-copy/wl-paste - klipper - qdbus A malicious user could rename or add programs with these names, tricking @@ -247,6 +250,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. @@ -532,6 +562,11 @@ def determine_clipboard(): else: return init_gtk_clipboard() + if ( + os.environ["XDG_SESSION_TYPE"] == "wayland" and + _executable_exists("wl-copy") + ): + return init_wl_clipboard() if _executable_exists("xsel"): return init_xsel_clipboard() if _executable_exists("xclip"): @@ -580,15 +615,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()]))) diff --git a/tests/test_pyperclip.py b/tests/test_pyperclip.py index 7519d94..ffdf2f6 100644 --- a/tests/test_pyperclip.py +++ b/tests/test_pyperclip.py @@ -13,6 +13,7 @@ init_dev_clipboard_clipboard, init_gtk_clipboard, init_qt_clipboard, init_xclip_clipboard, init_xsel_clipboard, + init_wl_clipboard, init_klipper_clipboard, init_no_clipboard) from pyperclip import init_windows_clipboard from pyperclip import init_wsl_clipboard @@ -168,6 +169,11 @@ class TestXSel(_TestClipboard): clipboard = init_xsel_clipboard() +class TestWlClipboard(_TestClipboard): + if _executable_exists("wl-copy"): + clipboard = init_wl_clipboard() + + class TestKlipper(_TestClipboard): if _executable_exists("klipper") and _executable_exists("qdbus"): clipboard = init_klipper_clipboard()